JWT(Json Web Token)는 클라이언트가 로그인한 후 서버로부터 토큰을 받아, 이후 요청마다 이 토큰을 보내면서 인증을 수행하는 방식이다. 서버는 세션을 유지하지 않아도 되기 때문에, 무상태(stateless) 아키텍처에 적합하다.
[사용자] ⇄ [Spring 서버] ⇄ [Redis]
↓
[JWT 발급 & 검증]
JwtService.javapublic String generateAccessToken(String sub) {
return jwtTokenProvider.createAccessToken(sub, List.of("ROLE_USER"));
}
public String generateRefreshToken(String sub) {
return jwtTokenProvider.createRefreshToken(sub);
}
HttpOnly, Secure)public void setTokenCookies(HttpServletResponse response, String sub, String accessToken, String refreshToken) {
redisTemplate.opsForValue().set("refresh:" + sub, refreshToken, 7, TimeUnit.DAYS);
ResponseCookie accessTokenCookie = ResponseCookie.from("accessToken", accessToken)
.httpOnly(true).secure(true).path("/").maxAge(3600).build();
ResponseCookie refreshTokenCookie = ResponseCookie.from("refreshToken", refreshToken)
.httpOnly(true).secure(true).path("/").maxAge(7 * 24 * 3600).build();
response.addHeader("Set-Cookie", accessTokenCookie.toString());
response.addHeader("Set-Cookie", refreshTokenCookie.toString());
}
public void removeTokenCookie(HttpServletResponse response) {
String refreshToken = jwtTokenProvider.resolveRefreshToken(request);
if (refreshToken != null && validateToken(refreshToken)) {
String userId = jwtTokenProvider.getUsernameFromToken(refreshToken);
redisTemplate.delete("refresh:" + userId);
}
// 쿠키 무효화
}
public void reissueToken(HttpServletRequest request, HttpServletResponse response) {
String refreshToken = jwtTokenProvider.resolveRefreshToken(request);
String userId = jwtTokenProvider.getUsernameFromToken(refreshToken);
String redisRefreshToken = redisTemplate.opsForValue().get("refresh:" + userId);
if (!refreshToken.equals(redisRefreshToken) || !validateToken(refreshToken)) {
throw new RuntimeException("Invalid refresh token");
}
String newAccessToken = generateAccessToken(userId);
ResponseCookie accessTokenCookie = ResponseCookie.from("accessToken", newAccessToken)
.httpOnly(true).secure(true).path("/").maxAge(3600).build();
response.addHeader("Set-Cookie", accessTokenCookie.toString());
}
Access Token은 stateless하게 처리되지만, Refresh Token은 탈취에 대비한 관리가 필요함.
즉, Refresh Token을 서버 측에서 상태 기반으로 관리할 수 있게 해주는 게 Redis의 역할이다.
jwt:
secret: yourSecretKey
access-token-expiration: 3600000
refresh-token-expiration: 604800000
spring:
redis:
host: localhost
port: 6379