인증/권한 업무 규칙과 전환 불변식 추출 #2
1 changed files with 101 additions and 0 deletions
|
|
@ -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<String> 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<String> 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<String> roles,
|
||||
Date issuedAt,
|
||||
Date expiration
|
||||
) {}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue