Skip to content

[refactor/#650] JWT 토큰 무효화 체계 및 RT 재사용 탐지 도입#651

Merged
kanghana1 merged 9 commits into
developfrom
refactor/#650
Jul 6, 2026
Merged

[refactor/#650] JWT 토큰 무효화 체계 및 RT 재사용 탐지 도입#651
kanghana1 merged 9 commits into
developfrom
refactor/#650

Conversation

@kanghana1

@kanghana1 kanghana1 commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

❤️ 기능 설명

기존 문제
JWT 인증에서 stateless의 약점인 "토큰 즉시 무효화 불가"를 보완하기 위해,
인증 필터가 매 요청 DB에서 회원을 조회해 상태를 확인하고 있었습니다. 세 가지 문제가 있었습니다.

  1. 무효화 수단이 "탈퇴" 하나뿐 -강제 로그아웃·탈취 대응이 불가능. 무효화가 계정 상태에 강결합
  2. 매 요청 DB 조회 -isActive 확인용으로 회원 엔티티 전체를 SELECT, 필터+getAuthentication에서 요청당 2회 중복 조회
  3. RT 재사용을 인지 못 함 - 재사용된 토큰은 거부되지만, 탈취를 판단하지도 탈취범의 병렬 세션을 폐기하지도 못함

변경사항

  1. tokenVersion 기반 무효화
  • 회원별 버전(Redis member:tokenVersion:<id>)을 토큰 ver claim에 주입
  • 무효화 = increment() → 해당 회원의 모든 AT/RT 일제 폐기
  • 탈퇴 시 자동 증가
  1. 인증 필터 최적화
  • findById+INACTIVE → Redis 정수 비교로 전환
  • getAuthentication을 claim 기반으로 변경 → 인증 요청당 DB 조회 2회 → 0회
  1. RT 재사용 탐지
  • 재발급 시 GETDEL이 null이고 JWT 서명이 유효+미만료면 "이미 소비된 발급 토큰 = 재사용(탈취)"으로 판정 → increment()로 전 체인 폐기
  • 오탐 방지용 grace window 30초(jwt.refresh-token-reuse-grace) — 동시 재발급 경쟁/재시도는 무효화 제외
  • 응답은 generic 401로 통일해 탐지 사실 비노출, 서버 로그만 기록

연결된 issue

연결된 issue를 자동으로 닫기 위해 아래 {이슈넘버}를 입력해주세요.

close #650



🩷 Approve 하기 전 확인해주세요!


✅ 체크리스트

  • PR 제목 규칙 잘 지켰는가?
  • 추가/수정사항을 설명하였는가?
  • 테스트 결과 사진을 넣었는가?
  • 이슈넘버를 적었는가?

@kanghana1 kanghana1 requested review from Dimo-2562 and dbalsk July 4, 2026 16:56
@kanghana1 kanghana1 self-assigned this Jul 4, 2026
@kanghana1 kanghana1 added 🛠️ FEAT This issue or pull request already exists 🌟 REFACT New feature or request labels Jul 4, 2026

@Dimo-2562 Dimo-2562 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

방향성은 정말 좋은 것 같습니다!!
고생하셨습니다!

Comment thread src/main/java/umc/cockple/demo/global/auth/TokenVersionRepository.java Outdated
Comment thread src/main/java/umc/cockple/demo/global/oauth2/service/KakaoOauthService.java Outdated
@kanghana1 kanghana1 merged commit 8cb3442 into develop Jul 6, 2026
1 check passed
@kanghana1 kanghana1 mentioned this pull request Jul 12, 2026
kanghana1 added a commit that referenced this pull request Jul 12, 2026
* [feat/#589] 안 읽은 메시지 조회 API 구현 (#619)

* refactor: 기존 안 읽은 메시지 조회 로직을 별도 서비스로 분리

* feat: 안 읽은 메시지 조회 API 구현

* refactor: 안 읽은 메시지 조회 로직을 애플리케이션 로직 대신 DB 쿼리로 처리

* test: 안 읽은 메시지 조회 통합 테스트 보강

* chore: 안 읽은 메시지 조회 API는 자주 사용되므로 인덱스 도입하여 조회 성능 개선

* fix: 채팅 배지가 안읽은 메시지 개수가 아닌 단순 boolean 값만 필요하므로 전체 API 수정

* refactor: 채팅방 조회 API에서 안읽은 메시지 조회를 배치 쿼리로 개선

* refactor: 메시지 전송 시 안 읽은 메시지 count를 배치 쿼리로 개선

* fix: 안 읽은 메시지 카운트 업데이트에서 쿼리 실패시 fallback 대신 실패로 변경

* [refactor/#600] ExerciseQueryService 유스케이스별 서비스 분리 (#623)

* refactor: Query 서비스에서 Repository를 직접 참조하지 않도록 변경

* refactor: 맵 쿼리 서비스 분리

* refactor: 추천 쿼리 서비스 분리

* refactor: 내 정보 관련 쿼리 서비스 분리

* refactor: 모임 운동 캘린더 조회 서비스 분리

* refactor: 게스트 조회 서비스 분리

* refactor: 라이프사이클 조회 서비스 분리

* refactor: Reader는 support/reader 패키지로 분리

* fix: 불필요한 Member 검증은 Jwt 필터에서 진행하므로 제거

* debug: 운동 수정용 상세 조회에서 모임장이나 부모임장 권한 로직이 없었떤 버그 제거

* refactor: 사용자 수 계산 쿼리를 운동이 없으면 호출하지 않도록 empty guard 추가

* refactor: contains를 loop를 돌며 호출하는걸 HashSet을 통해 시간복잡도 개선

* refactor: lookup 서비스의 에러는 그 도메인의 에러를 던지도록 변경

* test: Member를 사용하지 않는 서비스에서는 검증 로직을 JWT 필터에 의존하므로 관련 테스트 실패 로직 제거

* test: 시간 의존적인 테스트가 종종 실패하는 문제를 개선

* [refactor/#626] 채팅 REST/WebSocket presentation 계층 정리 (#627)

* refactor: chat 도메인 presentation 계층 정리

* refactor: 웹소켓 메시지 전송 클래스 위치 변경

* refactor: 웹소켓 메시지 전송 클래스 네이밍 명확화

* refactor: Handler는 단순 웹소켓 생명주기 관리로 축소, 메시지 처리는 Dispatcher로 위임

* refactor: request 타입별 메시지 처리를 CommandHandler 책임으로 위임

* refactor: 컨버터에서 웹소켓 전용 로직은 별도 클래스로 분리

* refactor: service 계층에서 presentation 계층에 의존성이 생기므로 일단 converter 패키지 아래로 재이동

* fix: 웹소켓 디스패쳐 로깅 개선 - payload 전체 찍기 X, 에러를 사용자에게 그대로 보내기 X

* docs: 로깅에서 메시지 타입이 아닌 멤버를 찍던 오류를 수정

* [chore/#628] 쿼리 카운트 테스트 인프라 구축 (#634)

* chore: 쿼리 카운트 테스트 인프라 구축

* chore: 스모크 테스트

* chore: 테스트 정합성을 위해 배치사이즈   설정을 테스트 프로파일에 추가

* chore: 캐시로 인한 쿼리 수가 맞지 않는 문제 해결

* [refactor/#630] Bookmark 도메인 N+1 제거 (#636)

* refactor: 관련 엔티티 fetch join

* refactor: 찜한 운동 인원수 count 쿼리화

* refactor: 찜 목록 역방향 fetch join

* test: Bookmark 찜 목록 N+1 쿼리카운트 테스트 추가

* chore: 불필요한 주석 제거

* refactor: set으로 묶기

* [refactor/#638] 운동 참여 인원 수 조회 로직 공통화 (#639)

* feat: 운동 도메인에 운동 참여 멤버수 계산 서비스를 별도 구현

* refactor: 운동 도메인에서 멤버 수 계산 로직을 CountLookupService에 위임하도록 변경
이때 같은 도메인의 서비스가 lookup을 쓰는 건 공통 로직을 묶는 한 어쩔 수 없는 trade-off임

* refactor: Bookmark 도메인에서도 CountLookupService에 운동 참여 멤버 수 로직을 위임하도록 변경

* fix: 사용하지 않게 된 리포지토리 쿼리를 제거하고, N+1 체크 테스트도 반영

* refactor: 운동 북마크 조회를 운동 도메인에 두던 걸 북마크 도메인으로 변경

* [feat/#625] 채팅 unread status WebSocket 이벤트 발행 (#637)

* feat: 채팅 전송 시 발신자를 제외하고 안 읽은 메시지 업데이트 이벤트 발송

* refactor: ChatSendService의 조회 메서드를 별도 Reader로 분리

* refactor: 채팅 전송 서비스에서 파일 첨부 로직을 별도 서비스로 분리

* refactor: 채팅 전송 서비스에서 첫 메시지 판별 로직을 별도로 분리

* refactor: 채팅 전송 서비스에서 이벤트 발행 책임을 별도 서비스로 분리

* refactor: send 관련 클래스들을 별도 패키지로 묶어서 정리

* improve: 시스템 메시지에서도 안 읽은 배지 업데이트 이벤트를 발행하도록 변경

* refactor: 이벤트 리스너는 service 하위로 이동

* refactor: 메시지 읽음 상태 처리 서비스도 send/support 하위로 이동

* refactor: 보낸 메시지 자동 읽음 처리 서비스를 네이밍을 명확하게 하고 메서드 명도 변경

* feat: 채팅방 입장 시 읽음 상태가 변동되므로 이벤트를 발행하도록 구현

* refactor: 구독 시 읽음 처리 서비스의 네이밍과 메서드명을 명확화

* refactor: redis 관련 클래스를 repository 아래로 이동

* refactor: 세션 관리 책임을 별도 서비스로 분리

* refactor: 웹소켓 전송 책임을 구독 서비스 밖으로 분리

* refactor: 브로드캐스트 로직을 별도 서비스로 분리

* refactor: 안 읽음 수 업데이트 브로드캐스트 로직을 별도로 분리

* refactor: 채팅방 목록 업데이트 브로드캐스트 로직도 별도로 분리

* refactor: 구독 서비스에서 redis store를 직접 참조하지 않도록 분리

* refactor: Subscription 서비스가 이름과 맞지 않게 너무 많은 책임을 가지고 있어 각 기능을 분리하고 네이밍을 명확화

* refactor: 이벤트 리스너를 별도로 분리

* improve: 탭을 여러 개 열었을 때, 같은 세션만 웹소켓을 닫도록 세션 정보도 전달

* improve: 안읽음 상태 업데이트를 활성 사용자 대상으로만 필터링 하도록 변경

* improve: 채팅 안읽음 메시지 조회 로직을 배치 쿼리로 변경

* refactor: 직렬화 책임을 별도 Encoder로 분리

* refactor: 메시지 전송은 SessionMessageSender에서만 진행하도록 변경

* docs: 현재 Redis 일관성 수준을 best-effort로 명시, 재시도 및 보상 정책은 추후 별도 이슈로

* test: AFTER-COMMIT 이후 실제 payload가 발행되는지 체크하는 서비스 추가

* chore: 불필요한 중복된 Transactional 어노테이션을 제거

* [debug/#611] 프로필 동시성 처리 문제 해결 (#642)

* fix: ProfileImg에 memberId unique 제약 추가

* feat: 낙관적 락, 아웃박스 로직을 이용한 정합성 로직 추가

* chore: 주석수정

* test: outbox 테스트 정리

* feat: 온보딩에서 프로필 등록하는 로직에도 변경사항 추가할 수 있도록 도메인 로직으로 분리

* fix: 컨트롤러가 Executor를 이용하도록 수정

* fix: 예외 범위 축소

* chore: 중복 이미지를 gcs에서 삭제할 수 있도록 적재하는 쿼리 추가

* [refactor/#624] 채팅 읽음 처리 쿼리 배치화 (#641)

* refactor: 구독 시 unread 배치 update + 배치 read로 변경

* refactor: 구독 후 lastReadMesageId 처리도 배치 쿼리로 변경

* refactor: 메시지 전송 시 자동 읽음 처리에서 활성 사용자의 lastReadMessageId 업데이트도 배치 쿼리로 변경

* refactor: 구독자 자동 읽음 서비스에서 메서드 추출을 통해 로직이 보이도록 변경

* refactor: 다른 클래스에서 참조하는 record를 클래수 내부 record에서 별도로 분리

* refactor: 읽음 상태 처리 repository 의존을 직접하는 대신 support 패키지의 클래스를 이용하도록 변경

* fix: 채팅 배치 읽음 처리에서 IN절이 너무 길어지는 것을 방지하여 500개 기준으로 CHUNK를 분리하도록 개선

* test: 배치 읽음 처리 및 count 로직을 리포지토리 단위 테스트로 검증

* fix: bulk 쿼리에서 제대로 처리되는지를 log로 검증

* fix: 마지막 메시지 Id를 stream.max()로 명확화

* refactor: 조회한 결과가 Id가 중간에 비어있을 수 있음을 메서드로 명시

* docs: 채팅방 멤버 테이블은 메시지 읽음 처리마다 updated_at 칼럼이 바뀔경우 부하가 될 수 있으므로 bulk 처리에서 배제하고 이를 명시

* fix: 채팅 멤버 읽음 상태 업데이트 클래스도 chunk 처리를 하도록 개선

* [refactor/#632] 안 읽은 알림 조회 최적화 (#645)

* [refactor/#631] 메달 개수 쿼리 최적화 (#644)

* refactor: 메달 개수 쿼리를 1개로 최적화

* chore: 주석 수정

* chore: 컨버터 롤백

* [chore/#582] 부하테스트용 FCM 인위적 지연 주입 (#652)

* chore: 부하테스트용 FCM 인위적 지연 주입

* chore: fcm지연 주입 추가

* chore: FCM 지연주입 값을 env(GitHub Variable)로 토글 가능하게 배관

* [feat/#647] 로깅 인프라 구축 (#649)

* feat: MDC 필터 추가 및 JWT 필터에서 MEMBER_ID 추가 및 삭제 로직 추가

* feat: 비동기 스레드의 MDC 전파를 위해 설정 및 TaskDecorator 추가

* chore: Logback 기반 콘솔 및 파일 로그 개선

* chore: 콘솔 로그에는 색상을 추가하여 가시성 개선

* chore: 로그 I/O를 비동기로 하도록 변경

* chore: 에러 로그는 별도로 중복 저장하여 운영 편리성 향상

* feat: 웹소켓에도 MDC를 별도로 적용

* chore: 도커 컴포즈 volume 설정으로 log 추가

* chore: local, staging, prod를 제외하고는 색상을 없애 xml 생성 오류 해결

* docs: 카카오 Authorization code를 로그에서 제거하여 보안 이슈 해결

* chore: 에러 로그에서는 ThresholdFilter를 통해 Error 로그만 받도록 변경

* chore: Error 제외 Appender는 block 되지 않도록 하고, 로그 레벨을 debug로 낮춤

* chore: 누락된 HTTP 메소드, uri, clientIp도 로깅하도록 변경

* test: MDC 로그 테스트 검증을 중복해서 하지 않도록 변경

* chore: 불필요한 줄바꿈 제거

* chore: 토큰 없는 요청의 경우 서버 에러가 인증 에러로 잘못 변환될 수 있으므로 별도 try-catch로 분리

* chore: Ai로 인해 줄바꿈이 잘못 변경된 걸 해결

* [refactor/#633] 회원 프로필 조회 최적화 (#646)

* [refactor/#582] FCM과 채팅 스레드풀 분리 (#653)

* feat: MDC 필터 추가 및 JWT 필터에서 MEMBER_ID 추가 및 삭제 로직 추가

* feat: 비동기 스레드의 MDC 전파를 위해 설정 및 TaskDecorator 추가

* refactor: 채팅과 fcm 스레드풀 분리

* fix: 포화로 거부된 작업은  로그만 찍도록 수정

---------

Co-authored-by: Dimo-2562 <chosehoon2@naver.com>

* [refactor/#650] JWT 토큰 무효화 체계 및 RT 재사용 탐지 도입 (#651)

* feat: tokenVersion 인프라 구축

* refactor: 인증 필터 tokenVersion 검증 전환 및 DB 조회 제거

* feat: 재사용 탐지 로직 추가

* fix: JWT claim에 type 추가

* fix: redis를 캐시로 사용하도록 수정

* fix: AT 무효화 검증 제거

* refactor: lua 스크립트를 이용해 동시성 이슈 방지

* test: 테스트 보강

* [feat/#654] Staging 계층 모니터링(OTLP) 도입  (#655)

* feat: grafana 세팅

* chore: 부하테스트 결과 파일 추적 제외

* [refactor/#656] 도메인 값 SSOT 외부화 (#657)

* refactor: 도메인값 SSOT 외부화

* refactor: terraform 도메인 변수화

* refactor: env 배선

* refactor: WS 포함

---------

Co-authored-by: Dmori <83327857+Dimo-2562@users.noreply.github.com>
Co-authored-by: Dimo-2562 <chosehoon2@naver.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🛠️ FEAT This issue or pull request already exists 🌟 REFACT New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[REFACT] 토큰 무효화 체계 개선 및 재사용 탐지 로직 추가

2 participants