Commit a6ef4aa1ad98cc50094b8d1d13dfa891b02ba3c6

Authored by zichun
1 parent ac69e557

feat(mod): tenant + jwt config + util REQ-MOD-001

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 +}