Repository navigation
[feat] 다중 기기 세션 관리 및 토큰 폐기 강화 - #131
Merged
Merged
Conversation
- 새 로그인마다 UUID jti를 발급하고, 회전 시 같은 jti로 재발급하는 오버로드 추가 - getSessionId로 jti 조회 (jti 도입 이전 토큰은 null) - 같은 초에 발급된 Refresh Token 값이 동일해지던 문제 해소 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- TokenRepository: RT:{userId}:{sessionId} + 세션 목록 ZSET(RT_SESSIONS:{userId})으로 변경, 사용자당 최대 5세션(초과 시 가장 오래된 세션 제거)
- 회전은 Lua 스크립트로 저장값 비교 후 교체해 동시 refresh 중 1건만 성공, 세션 목록 TTL도 함께 연장
- 로그인(OAuth2·Dev)마다 새 세션 저장 — 다른 기기의 Refresh Token을 덮어쓰지 않음
- refresh: 해당 세션만 검증·교체, jti 없는 기존 토큰은 REFRESH_TOKEN_INVALID(재로그인)
- logout: Refresh Token 쿠키로 현재 기기 세션만 삭제 (쿠키 없거나 무효·타 사용자 토큰이면 삭제 생략, 200 유지)
- withdraw: 모든 기기 세션 삭제
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- AT에 typ=access, RT에 typ=refresh claim 추가, 인증 필터는 typ=access만 인증 (로그아웃·세션 제거 후에도 서명이 유효한 RT가 Bearer로 통과하던 문제 차단) - JwtProvider.parseRefreshToken: typ·jti를 검증해 userId·sessionId 반환 (getSessionId·getUserId 대체) - 회전 Lua 스크립트: 저장값 불일치 시 이미 회전된 RT의 재사용으로 보고 해당 세션만 원자적으로 폐기 - AuthService: 재사용 감지 시 WARN 로그 후 REFRESH_TOKEN_INVALID Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AuthController가 secure(true)를 하드코딩해 로컬(http)에서 refresh 후 쿠키가 Secure로 재발급되던 문제를 OAuth2SuccessHandler와 같이 app.cookie.secure 설정을 따르도록 수정 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedNext included review available in 2 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (14)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
You-Hyuk
added a commit
to Cominggg/Specification
that referenced
this pull request
Sep 27, 2026
BE 구현 완료(Cominggg/Backend#131)에 따라 "BE 반영 전까지 사용자당 세션 1개" 안내를 제거한다. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
관련 이슈
Closes #130
변경 개요
Refresh Token(RT)이 사용자당 1개(
RT:{userId})만 저장되어 여러 기기에서 동시에 로그인할 수 없었다. 새 기기에서 로그인하면 기존 기기의 RT를 덮어써서, 기존 기기는 30분 뒤 로그아웃됐다. 한 기기에서 로그아웃하면 모든 기기의 RT가 함께 삭제되는 문제도 있었다.RT를 기기(세션) 단위로 저장하도록 바꾸고, 보안 리뷰에서 나온 토큰 폐기 관련 빈틈도 함께 막는다.
정책은 명세에서 먼저 확정했다:
spec/policy/auth-policy.md"다중 기기 로그인 정책" (Cominggg/Specification@03edf79, @18a1a27).변경사항
auth/jwt/JwtProvider.javajti, AT/RT에typclaim(access/refresh) 추가.parseRefreshToken(typ·jti 검증 → userId·sessionId),isAccessToken추가.getUserId는 쓰는 곳이 없어져 삭제auth/jwt/RefreshTokenClaims.javaauth/jwt/JwtAuthenticationFilter.javatyp=access토큰만 인증auth/repository/TokenRepository.javaRT:{userId}:{sessionId}+ 세션 목록 ZSETRT_SESSIONS:{userId}. 최대 5세션(초과 시 가장 오래된 세션 제거), Lua 스크립트로 원자적 회전·재사용 감지auth/service/AuthService.javaauth/controller/AuthController.java/logout에서 RT 쿠키 수신, RT 쿠키의secure를app.cookie.secure설정으로 변경auth/oauth2/OAuth2SuccessHandler.java,auth/dev/DevAuthService.java주요 구현 내용
세션 저장 구조
RT:{userId}:{sessionId}→ 세션별 RT (TTL 7일)RT_SESSIONS:{userId}→ 세션 목록 ZSET (score = 생성 시각)회전·재사용 감지 (Lua, 원자적)
ROTATEDREUSE_DETECTEDSESSION_NOT_FOUND토큰 종류 구분
typ=access토큰만 인증하고, refresh와 logout은typ=refresh토큰만 받는다.테스트
1ac5d1f,2bc7e03) 이전 상태였다. 최종 상태는 로컬에서 실행 확인하지 않았다../gradlew test779건 통과EVAL로 직접 실행해 확인 — 일치 / 재사용(해당 세션만 폐기, 다른 세션 유지) / 세션 없음리뷰어 참고사항
typ·jti가 없어서 거부된다. 기존RT:{userId}키는 옮기지 않고 TTL 7일 뒤 자연히 사라진다. 공지는 하지 않기로 했다.TokenRepository의 API를 바꾸면 호출부도 같은 커밋에서 바뀌어야 해서, 세션 저장과 호출부 변경은 한 커밋에 묶었다.코드 리뷰
변경사항 요약
RefreshTokenClaims포함), 테스트 6개 파일/be-review: 🔴 0건security-reviewer2회(세션 구조, 토큰 폐기 강화): 모두 critical 0 / warning 0/security-review: 신뢰도 8 이상 취약점 없음검토 결과
🟡 warning
TokenRepository.java:rotate()의 매개변수가 5개,saveSession()이 4개다. 프로젝트 규칙은 3개를 넘으면 DTO로 묶도록 한다.→ 호출하는 곳이 1~2곳이고 기존
save(userId, token, ttl)과 같은 형태라서 유지했다. 한 곳에서만 쓸 record를 만들지 않기 위해서다.🔵 suggestion
AuthService.java: 재사용을 감지하거나 탈퇴해도, 이미 발급된 다른 AT는 최대 30분 동안 유효하다.→ 30분 AT 정책 범위라 받아들였다. 막으려면 요청마다 Redis 조회가 늘어난다.
OAuth2SuccessHandler.java,DevAuthService.java: 방금 발급한 RT를 다시 파싱해 sessionId를 얻는다.→ 서명 검증이 한 번 더 드는 정도라 유지했다.
TokenRepository.java: 회전 Lua 스크립트가 서로 다른 hash slot의 키 2개를 함께 다룬다.→ 지금은 Redis 한 대라 문제없다. Redis Cluster로 옮기면 키에
{userId}hash tag가 필요하다.DevAuthService.java: 테스트가 없다.→ 원래 없었고 local 프로필 전용이라 이번 범위에서 제외했다.