Commit b7ed804af2750beefdf6911fdf3e17a7a3a940a8
1 parent
b2b67f47
feat(usr): JwtTokenProvider sign/parse REQ-USR-004
Showing
2 changed files
with
103 additions
and
0 deletions
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 | +} |
-
mentioned in commit d439c0d9