Commit a6ef4aa1ad98cc50094b8d1d13dfa891b02ba3c6
1 parent
ac69e557
feat(mod): tenant + jwt config + util REQ-MOD-001
Showing
3 changed files
with
110 additions
and
0 deletions
backend/src/main/java/com/xly/erp/common/security/JwtUtil.java
0 → 100644
| 1 | +package com.xly.erp.common.security; | |
| 2 | + | |
| 3 | +import com.xly.erp.common.config.StubSecurityProperties; | |
| 4 | +import com.xly.erp.common.exception.BizException; | |
| 5 | +import io.jsonwebtoken.Jwts; | |
| 6 | +import io.jsonwebtoken.JwtException; | |
| 7 | +import org.springframework.beans.factory.annotation.Autowired; | |
| 8 | +import org.springframework.stereotype.Component; | |
| 9 | + | |
| 10 | +import javax.crypto.SecretKey; | |
| 11 | +import javax.crypto.spec.SecretKeySpec; | |
| 12 | +import java.nio.charset.StandardCharsets; | |
| 13 | +import java.time.Duration; | |
| 14 | +import java.util.Date; | |
| 15 | + | |
| 16 | +@Component | |
| 17 | +public class JwtUtil { | |
| 18 | + | |
| 19 | + private static final Duration TTL = Duration.ofHours(8); | |
| 20 | + | |
| 21 | + private final SecretKey key; | |
| 22 | + | |
| 23 | + public JwtUtil(String secret) { | |
| 24 | + this.key = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); | |
| 25 | + } | |
| 26 | + | |
| 27 | + @Autowired | |
| 28 | + public JwtUtil(StubSecurityProperties props) { | |
| 29 | + this(props.getJwtSecret()); | |
| 30 | + } | |
| 31 | + | |
| 32 | + public String sign(String userNo) { | |
| 33 | + Date now = new Date(); | |
| 34 | + return Jwts.builder() | |
| 35 | + .subject(userNo) | |
| 36 | + .issuedAt(now) | |
| 37 | + .expiration(new Date(now.getTime() + TTL.toMillis())) | |
| 38 | + .signWith(key) | |
| 39 | + .compact(); | |
| 40 | + } | |
| 41 | + | |
| 42 | + public String parse(String token) { | |
| 43 | + try { | |
| 44 | + return Jwts.parser() | |
| 45 | + .verifyWith(key) | |
| 46 | + .build() | |
| 47 | + .parseSignedClaims(token) | |
| 48 | + .getPayload() | |
| 49 | + .getSubject(); | |
| 50 | + } catch (JwtException | IllegalArgumentException e) { | |
| 51 | + throw new BizException(20001, "未认证或 token 已失效"); | |
| 52 | + } | |
| 53 | + } | |
| 54 | +} | ... | ... |
backend/src/test/java/com/xly/erp/common/security/JwtUtilTest.java
0 → 100644
| 1 | +package com.xly.erp.common.security; | |
| 2 | + | |
| 3 | +import com.xly.erp.common.exception.BizException; | |
| 4 | +import org.junit.jupiter.api.Test; | |
| 5 | + | |
| 6 | +import static org.assertj.core.api.Assertions.assertThat; | |
| 7 | +import static org.assertj.core.api.Assertions.assertThatThrownBy; | |
| 8 | + | |
| 9 | +class JwtUtilTest { | |
| 10 | + | |
| 11 | + private static final String SECRET = "f8d4be76bff13bf32fa33ca0b14a4b152ad01ca5719f57df18ec4ecf2370b235"; | |
| 12 | + | |
| 13 | + private final JwtUtil jwtUtil = new JwtUtil(SECRET); | |
| 14 | + | |
| 15 | + @Test | |
| 16 | + void signAndParse_roundTrip() { | |
| 17 | + String token = jwtUtil.sign("ALICE001"); | |
| 18 | + assertThat(token).isNotBlank(); | |
| 19 | + assertThat(jwtUtil.parse(token)).isEqualTo("ALICE001"); | |
| 20 | + } | |
| 21 | + | |
| 22 | + @Test | |
| 23 | + void parseTamperedToken_throwsBizException20001() { | |
| 24 | + String token = jwtUtil.sign("ALICE001"); | |
| 25 | + String tampered = token.substring(0, token.length() - 4) + "XXXX"; | |
| 26 | + assertThatThrownBy(() -> jwtUtil.parse(tampered)) | |
| 27 | + .isInstanceOf(BizException.class) | |
| 28 | + .hasFieldOrPropertyWithValue("code", 20001); | |
| 29 | + } | |
| 30 | + | |
| 31 | + @Test | |
| 32 | + void parseGarbageToken_throwsBizException20001() { | |
| 33 | + assertThatThrownBy(() -> jwtUtil.parse("not.a.real.jwt")) | |
| 34 | + .isInstanceOf(BizException.class) | |
| 35 | + .hasFieldOrPropertyWithValue("code", 20001); | |
| 36 | + } | |
| 37 | +} | ... | ... |
backend/src/test/java/com/xly/erp/common/security/TestJwtHelper.java
0 → 100644
| 1 | +package com.xly.erp.common.security; | |
| 2 | + | |
| 3 | +import org.springframework.beans.factory.annotation.Autowired; | |
| 4 | +import org.springframework.stereotype.Component; | |
| 5 | + | |
| 6 | +@Component | |
| 7 | +public class TestJwtHelper { | |
| 8 | + | |
| 9 | + private final JwtUtil jwtUtil; | |
| 10 | + | |
| 11 | + @Autowired | |
| 12 | + public TestJwtHelper(JwtUtil jwtUtil) { | |
| 13 | + this.jwtUtil = jwtUtil; | |
| 14 | + } | |
| 15 | + | |
| 16 | + public String signFor(String userNo) { | |
| 17 | + return jwtUtil.sign(userNo); | |
| 18 | + } | |
| 19 | +} | ... | ... |