-
Notifications
You must be signed in to change notification settings - Fork 0
[fix] #196 - 재발급 에러 해결 #198
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
7401a95
9ede751
ad4428c
65da8b7
45908cf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,7 @@ | |
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Objects; | ||
| import java.util.Set; | ||
| import java.util.Optional; | ||
| import java.util.UUID; | ||
| import java.util.concurrent.TimeUnit; | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
@@ -21,6 +21,8 @@ public class RefreshTokenService { | |
| private final JwtTokenProvider jwtTokenProvider; | ||
|
|
||
| private static final String KEY_PREFIX = "refresh:"; | ||
| private static final String ROTATED_PREFIX = "refresh:rotated:"; | ||
| private static final long ROTATION_GRACE_SECONDS = 5; | ||
|
|
||
| public String saveRefreshToken(String userId, String refreshToken){ | ||
| String sessionId = UUID.randomUUID().toString(); | ||
|
|
@@ -63,4 +65,24 @@ public void deleteAllRefreshTokens(String userId) { | |
| public boolean isRefreshTokenValid(String userId, String sessionId, String refreshToken) { | ||
| return Objects.equals(refreshToken, getRefreshToken(userId, sessionId)); | ||
| } | ||
|
|
||
| public String rotateRefreshToken(String userId, String oldSessionId, String newRefreshToken) { | ||
| String newSessionId = saveRefreshToken(userId, newRefreshToken); | ||
|
|
||
| redisTemplate.opsForValue().set( | ||
| ROTATED_PREFIX + userId + ":" + oldSessionId, | ||
| newSessionId, | ||
| ROTATION_GRACE_SECONDS, | ||
| TimeUnit.SECONDS | ||
| ); | ||
|
|
||
| deleteRefreshToken(userId, oldSessionId); | ||
|
Comment on lines
+70
to
+79
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift 리프레시 토큰 회전을 원자적으로 처리해야 합니다. 동시 요청 두 개가 모두 기존 토큰 검사를 통과할 수 있습니다. 그러면 두 요청이 각각 새 세션을 저장합니다. 이후 요청이 rotation 매핑을 덮어쓰지만, 먼저 생성한 리프레시 토큰도 만료 시간까지 유효하게 남습니다. 기존 토큰 값 비교, 새 세션 저장, rotation 매핑 저장, 기존 세션 삭제를 하나의 Redis Lua 스크립트 또는 원자적 compare-and-set 흐름으로 처리하세요. 이미 회전된 경우에는 저장된 단일 세션 ID를 반환해야 합니다. 🤖 Prompt for AI Agents |
||
| return newSessionId; | ||
| } | ||
|
|
||
| public Optional<String> findRotatedSessionId(String userId, String oldSessionId) { | ||
| return Optional.ofNullable( | ||
| redisTemplate.opsForValue().get(ROTATED_PREFIX + userId + ":" + oldSessionId) | ||
| ); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[p1] legacy 쿠키 정리가 성공 응답인
reissueResponse()에만 들어가 있어서, 중복 쿠키로 인해authService.reissue()가 AUTH_401/USER_404를 던지는 경우에는 이 코드까지 도달하지 못할 것 같습니다. 그러면 문제가 있는 쿠키가 브라우저에 계속 남아 똑같은 오류가 남을 것 같아요.reissue의 성공/실패와 무관하게 legacy 만료 헤더가 내려가도록 Filter, ResponseBodyAdvice 또는 예외 응답 경로에서 처리하거나, 서비스 호출 전에 중복 쿠키를 안전하게 정리/선택하는 방식이 필요해 보입니다.