Commit b7ed804af2750beefdf6911fdf3e17a7a3a940a8

Authored by zichun
1 parent b2b67f47

feat(usr): JwtTokenProvider sign/parse REQ-USR-004

backend/src/main/java/com/xly/erp/module/usr/security/JwtTokenProvider.java 0 → 100644
  1 +package com.xly.erp.module.usr.security;
  2 +
  3 +import io.jsonwebtoken.Claims;
  4 +import io.jsonwebtoken.Jwts;
  5 +import io.jsonwebtoken.security.Keys;
  6 +import org.springframework.beans.factory.annotation.Value;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +import javax.crypto.SecretKey;
  10 +import java.nio.charset.StandardCharsets;
  11 +import java.time.Instant;
  12 +import java.util.Date;
  13 +
  14 +/** REQ-USR-004 JWT 签发 / 校验封装。HS256,secret 来自 .env.local JWT_SECRET。 */
  15 +@Component
  16 +public class JwtTokenProvider {
  17 +
  18 + private final SecretKey key;
  19 + private final long expiresInSeconds;
  20 +
  21 + public JwtTokenProvider(@Value("${erp.jwt.secret}") String secret,
  22 + @Value("${erp.jwt.expires-in-seconds}") long expiresInSeconds) {
  23 + this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
  24 + this.expiresInSeconds = expiresInSeconds;
  25 + }
  26 +
  27 + public long getExpiresInSeconds() {
  28 + return expiresInSeconds;
  29 + }
  30 +
  31 + public String sign(int uid, String username, String userType) {
  32 + Instant now = Instant.now();
  33 + return Jwts.builder()
  34 + .subject(username)
  35 + .claim("uid", uid)
  36 + .claim("type", userType)
  37 + .issuedAt(Date.from(now))
  38 + .expiration(Date.from(now.plusSeconds(expiresInSeconds)))
  39 + .signWith(key)
  40 + .compact();
  41 + }
  42 +
  43 + public Claims parse(String token) {
  44 + return Jwts.parser()
  45 + .verifyWith(key)
  46 + .build()
  47 + .parseSignedClaims(token)
  48 + .getPayload();
  49 + }
  50 +}
... ...
backend/src/test/java/com/xly/erp/module/usr/security/JwtTokenProviderTest.java 0 → 100644
  1 +package com.xly.erp.module.usr.security;
  2 +
  3 +import io.jsonwebtoken.Claims;
  4 +import io.jsonwebtoken.ExpiredJwtException;
  5 +import io.jsonwebtoken.Jwts;
  6 +import io.jsonwebtoken.security.Keys;
  7 +import org.junit.jupiter.api.Test;
  8 +
  9 +import javax.crypto.SecretKey;
  10 +import java.nio.charset.StandardCharsets;
  11 +import java.time.Instant;
  12 +import java.util.Date;
  13 +
  14 +import static org.assertj.core.api.Assertions.assertThat;
  15 +import static org.assertj.core.api.Assertions.assertThatThrownBy;
  16 +
  17 +class JwtTokenProviderTest {
  18 +
  19 + private static final String SECRET = "f8d4be76bff13bf32fa33ca0b14a4b152ad01ca5719f57df18ec4ecf2370b235";
  20 + private static final long EXPIRES_IN = 7200L;
  21 +
  22 + @Test
  23 + void signAndParse_returnsClaims() {
  24 + JwtTokenProvider provider = new JwtTokenProvider(SECRET, EXPIRES_IN);
  25 +
  26 + String token = provider.sign(42, "alice", "普通用户");
  27 +
  28 + Claims claims = provider.parse(token);
  29 + assertThat(claims.getSubject()).isEqualTo("alice");
  30 + assertThat(claims.get("uid", Integer.class)).isEqualTo(42);
  31 + assertThat(claims.get("type", String.class)).isEqualTo("普通用户");
  32 + assertThat(claims.getExpiration().getTime() - claims.getIssuedAt().getTime())
  33 + .isEqualTo(EXPIRES_IN * 1000L);
  34 + }
  35 +
  36 + @Test
  37 + void parseExpiredToken_throwsExpiredJwtException() {
  38 + // 用同一 secret 手工签一个已过期 token
  39 + SecretKey key = Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8));
  40 + Instant now = Instant.now();
  41 + String expired = Jwts.builder()
  42 + .subject("alice")
  43 + .issuedAt(Date.from(now.minusSeconds(7200)))
  44 + .expiration(Date.from(now.minusSeconds(60)))
  45 + .signWith(key)
  46 + .compact();
  47 +
  48 + JwtTokenProvider provider = new JwtTokenProvider(SECRET, EXPIRES_IN);
  49 +
  50 + assertThatThrownBy(() -> provider.parse(expired))
  51 + .isInstanceOf(ExpiredJwtException.class);
  52 + }
  53 +}
... ...