From 7fbd1447e12d9b017f91dc05bbaf1066636e6e7b Mon Sep 17 00:00:00 2001 From: zichun Date: Fri, 8 May 2026 09:48:56 +0800 Subject: [PATCH] feat(usr): JwtUtil generate + parse access/refresh token REQ-USR-004 --- backend/src/main/java/com/example/erp/common/util/JwtUtil.java | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ backend/src/main/java/com/example/erp/config/JwtProperties.java | 16 ++++++++++++++++ backend/src/test/java/com/example/erp/common/JwtUtilTest.java | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+), 0 deletions(-) create mode 100644 backend/src/main/java/com/example/erp/common/util/JwtUtil.java create mode 100644 backend/src/main/java/com/example/erp/config/JwtProperties.java create mode 100644 backend/src/test/java/com/example/erp/common/JwtUtilTest.java diff --git a/backend/src/main/java/com/example/erp/common/util/JwtUtil.java b/backend/src/main/java/com/example/erp/common/util/JwtUtil.java new file mode 100644 index 0000000..386b4ba --- /dev/null +++ b/backend/src/main/java/com/example/erp/common/util/JwtUtil.java @@ -0,0 +1,75 @@ +package com.example.erp.common.util; + +import com.example.erp.common.constants.AuthErrorCode; +import com.example.erp.common.exception.BizException; +import com.example.erp.config.JwtProperties; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +@Component +@RequiredArgsConstructor +public class JwtUtil { + + private final JwtProperties properties; + + private SecretKey key() { + return Keys.hmacShaKeyFor(properties.getSecret().getBytes(StandardCharsets.UTF_8)); + } + + public String generateAccessToken(String userId, String username, String userType, String brandId) { + long now = System.currentTimeMillis(); + return Jwts.builder() + .subject(userId) + .claim("username", username) + .claim("userType", userType) + .claim("brandId", brandId) + .issuedAt(new Date(now)) + .expiration(new Date(now + properties.getAccessTokenExpiry() * 1000)) + .signWith(key(), Jwts.SIG.HS256) + .compact(); + } + + public String generateRefreshToken(String userId, String brandId) { + long now = System.currentTimeMillis(); + return Jwts.builder() + .subject(userId) + .claim("brandId", brandId) + .claim("type", "refresh") + .issuedAt(new Date(now)) + .expiration(new Date(now + properties.getRefreshTokenExpiry() * 1000)) + .signWith(key(), Jwts.SIG.HS256) + .compact(); + } + + public Claims parseAccessToken(String token) { + return doParse(token); + } + + public Claims parseRefreshToken(String token) { + Claims claims = doParse(token); + if (!"refresh".equals(claims.get("type", String.class))) { + throw new BizException(AuthErrorCode.REFRESH_TOKEN_INVALID, "Refresh Token 已失效,请重新登录"); + } + return claims; + } + + private Claims doParse(String token) { + try { + return Jwts.parser() + .verifyWith(key()) + .build() + .parseSignedClaims(token) + .getPayload(); + } catch (JwtException e) { + throw new BizException(AuthErrorCode.REFRESH_TOKEN_INVALID, "Token 已失效,请重新登录"); + } + } +} diff --git a/backend/src/main/java/com/example/erp/config/JwtProperties.java b/backend/src/main/java/com/example/erp/config/JwtProperties.java new file mode 100644 index 0000000..dd172ab --- /dev/null +++ b/backend/src/main/java/com/example/erp/config/JwtProperties.java @@ -0,0 +1,16 @@ +package com.example.erp.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Getter +@Setter +@Component +@ConfigurationProperties(prefix = "jwt") +public class JwtProperties { + private String secret; + private long accessTokenExpiry; + private long refreshTokenExpiry; +} diff --git a/backend/src/test/java/com/example/erp/common/JwtUtilTest.java b/backend/src/test/java/com/example/erp/common/JwtUtilTest.java new file mode 100644 index 0000000..8fbce7e --- /dev/null +++ b/backend/src/test/java/com/example/erp/common/JwtUtilTest.java @@ -0,0 +1,56 @@ +package com.example.erp.common; + +import com.example.erp.common.exception.BizException; +import com.example.erp.common.util.JwtUtil; +import com.example.erp.config.JwtProperties; +import io.jsonwebtoken.Claims; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class JwtUtilTest { + + private JwtUtil jwtUtil; + + @BeforeEach + void setUp() { + JwtProperties props = new JwtProperties(); + props.setSecret("testSecretKey32CharactersMinimumXXXXX"); + props.setAccessTokenExpiry(86400L); + props.setRefreshTokenExpiry(604800L); + jwtUtil = new JwtUtil(props); + } + + @Test + void generateAndParseAccessToken_containsAllClaims() { + String token = jwtUtil.generateAccessToken("u1", "admin", "超级管理员", "b1"); + Claims claims = jwtUtil.parseAccessToken(token); + + assertEquals("u1", claims.getSubject()); + assertEquals("admin", claims.get("username", String.class)); + assertEquals("超级管理员", claims.get("userType", String.class)); + assertEquals("b1", claims.get("brandId", String.class)); + assertNotNull(claims.getExpiration()); + } + + @Test + void parseRefreshToken_withAccessToken_throws40103() { + String accessToken = jwtUtil.generateAccessToken("u1", "admin", "普通用户", "b1"); + BizException ex = assertThrows(BizException.class, () -> jwtUtil.parseRefreshToken(accessToken)); + assertEquals(40103, ex.getCode()); + } + + @Test + void parseAccessToken_withExpiredToken_throws40103() { + JwtProperties expiredProps = new JwtProperties(); + expiredProps.setSecret("testSecretKey32CharactersMinimumXXXXX"); + expiredProps.setAccessTokenExpiry(-1L); + expiredProps.setRefreshTokenExpiry(604800L); + JwtUtil expiredJwtUtil = new JwtUtil(expiredProps); + + String token = expiredJwtUtil.generateAccessToken("u1", "admin", "普通用户", "b1"); + BizException ex = assertThrows(BizException.class, () -> jwtUtil.parseAccessToken(token)); + assertEquals(40103, ex.getCode()); + } +} -- libgit2 0.22.2