From a3d57302e7cb691caddd4ed685a9c1e05ed8873c Mon Sep 17 00:00:00 2001 From: forge-bot Date: Mon, 13 Jul 2026 10:30:33 +0000 Subject: [PATCH] =?UTF-8?q?=EC=9D=B8=EC=A6=9D/=EA=B6=8C=ED=95=9C=20?= =?UTF-8?q?=EC=97=85=EB=AC=B4=20=EA=B7=9C=EC=B9=99=EA=B3=BC=20=EC=A0=84?= =?UTF-8?q?=ED=99=98=20=EB=B6=88=EB=B3=80=EC=8B=9D=20=EC=B6=94=EC=B6=9C=20?= =?UTF-8?q?(codex-bais-final3-20260713-192718-BAIS-SPRING-ANA-AUTH-001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/security/JwtTokenProvider.java | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 auth-migration/src/main/java/com/example/auth/security/JwtTokenProvider.java diff --git a/auth-migration/src/main/java/com/example/auth/security/JwtTokenProvider.java b/auth-migration/src/main/java/com/example/auth/security/JwtTokenProvider.java new file mode 100644 index 0000000..17b2424 --- /dev/null +++ b/auth-migration/src/main/java/com/example/auth/security/JwtTokenProvider.java @@ -0,0 +1,101 @@ +package com.example.auth.security; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.List; + +/** + * JWT Token Provider - preserves AUTH-010, AUTH-011, AUTH-003 invariants. + * JWT structure: {sub, roles[], exp, iat} + * Access token TTL: 30 minutes (AUTH-003) + * Refresh token: 7 days, one-time use (AUTH-011) + */ +@Component +public class JwtTokenProvider { + + private static final long ACCESS_TOKEN_TTL_MS = 30 * 60 * 1000; + private static final long REFRESH_TOKEN_TTL_MS = 7 * 24 * 60 * 60 * 1000; + + private final SecretKey secretKey; + private final TokenBlacklistService blacklistService; + + public JwtTokenProvider(TokenBlacklistService blacklistService) { + this.secretKey = Keys.hmacShaKeyFor( + System.getenv("JWT_SECRET").getBytes(StandardCharsets.UTF_8)); + this.blacklistService = blacklistService; + } + + public String generateAccessToken(String username, List roles) { + Date now = new Date(); + Date expiry = new Date(now.getTime() + ACCESS_TOKEN_TTL_MS); + + return Jwts.builder() + .subject(username) + .claim("roles", roles) + .issuedAt(now) + .expiration(expiry) + .signWith(secretKey) + .compact(); + } + + public String generateRefreshToken(String username) { + Date now = new Date(); + Date expiry = new Date(now.getTime() + REFRESH_TOKEN_TTL_MS); + + return Jwts.builder() + .subject(username) + .claim("type", "refresh") + .issuedAt(now) + .expiration(expiry) + .signWith(secretKey) + .compact(); + } + + public JwtClaims getClaims(String token) { + Claims claims = Jwts.parser() + .verifyWith(secretKey) + .build() + .parseSignedClaims(token) + .getPayload(); + + @SuppressWarnings("unchecked") + List roles = claims.get("roles", List.class); + + return new JwtClaims( + claims.getSubject(), + roles != null ? roles : List.of(), + claims.getIssuedAt(), + claims.getExpiration() + ); + } + + public boolean validateToken(String token) { + try { + if (blacklistService.isBlacklisted(token)) { + return false; // AUTH-012: revoked tokens invalid + } + + Jwts.parser() + .verifyWith(secretKey) + .build() + .parseSignedClaims(token); + return true; + } catch (JwtException | IllegalArgumentException e) { + return false; + } + } + + public record JwtClaims( + String subject, + List roles, + Date issuedAt, + Date expiration + ) {} +}