728x90
1. 도입
JWT 기반 인증 시스템을 운영하면서 가장 흔하게 마주하는 고민 중 하나는 **"로그아웃을 어떻게 처리할 것인가?"**이다.
JWT는 stateless(상태 없음) 구조로 설계되어 있어, 일반적인 세션 기반 시스템처럼 서버에서 상태를 삭제할 수 없기 때문이다.
우리 서비스는 이미 AccessToken / RefreshToken을 분리하여 운영하고 있고,
Redis 기반의 RefreshToken 저장소, Outbox + Kafka 기반의 탈퇴 이벤트 시스템도 갖추고 있다.
이번에는 여기에 로그아웃 처리와 블랙리스트 기반 무효화 기능을 추가하였다.
2. 설계 목표
AccessToken의 즉각적 무효화 (만료 시간 이전에도)
RefreshToken의 기기별/전체 삭제
Redis를 통한 블랙리스트 처리 구현
BFF 구조와 역할 분리:
- BFF는 AccessToken을 파싱해
jti를 넘김 - Auth는
jti를 Redis에 등록해 토큰을 무효화함
- BFF는 AccessToken을 파싱해
3. 설계 고려사항
| 항목 | 결정 사항 |
|---|---|
| AccessToken 무효화 | Redis에 blacklist:{jti} 키로 저장 |
| 만료 처리 방식 | 토큰의 TTL과 Redis key TTL을 동일하게 유지 |
| RefreshToken 삭제 | Redis에서 refresh:{userId}:{deviceId} 키 제거 |
| 블랙리스트 확인 | 로그인 필터 혹은 인증 인터셉터에서 검사 |
| JWT 구조 확장 | jti(JWT ID) 필드 포함 필수 |
참고:
jti는 JWT의 고유 식별자 claim으로, 로그아웃 시 이 값으로 Redis에 무효화를 등록한다.
4. 구현 설명
JwtTokenProvider (jti 포함)
String jti = UUID.randomUUID().toString();
return Jwts.builder()
.setSubject(subject)
.addClaims(claims)
.setIssuedAt(now)
.setExpiration(expiry)
.setId(jti) // <== 핵심
.signWith(key)
.compact();
LogoutService
public void logout(String accessToken, String userId, String deviceId) {
refreshTokenService.delete(userId, deviceId);
String jti = jwtTokenProvider.getJti(accessToken);
Duration ttl = jwtTokenProvider.getAccessTokenTTL();
redisTemplate.opsForValue()
.set("blacklist:" + jti, "1", ttl);
}
- 기기별 로그아웃: 해당 디바이스의 RefreshToken만 제거
- 전체 로그아웃: 모든 RefreshToken 제거 + AccessToken 무효화
Redis 구조
| Key | Value | TTL |
|---|---|---|
refresh:{userId}:{deviceId} |
JWT RefreshToken | 남은 만료 시간 |
blacklist:{jti} |
"1" |
AccessToken TTL |
5. 테스트 전략
- 로그아웃 후 동일 토큰 사용 시 접근 차단 (인증 필터에서 블랙리스트 검사)
jti가 없는 경우 → 예외 처리- TTL 설정된 Redis 키 자동 만료 확인
- RefreshToken 기기별/전체 삭제 확인
6. 회고 및 확장 가능성
- JWT는 stateless하지만, 실질적인 상태 저장소(Redis)를 조합하여 제어 가능하다.
jti기반 블랙리스트는 간단하지만 효과적인 대안이다.- 향후 BFF에서 토큰 검증을 담당하게 되면, Auth에서는 jti만 기반으로 블랙리스트 여부만 판단하면 되므로 책임이 명확해진다.
- 블랙리스트 외에도,
LogoutEvent를 Kafka로 발행해 로그아웃 이벤트 분석도 가능하다.
마무리
지금까지 구현한 로그아웃 기능은 단순한 세션 제거를 넘어,
토큰 기반 인증 구조에서 상태 관리를 안전하게 수행할 수 있는 패턴을 반영한 구조다.
특히, jti 기반 블랙리스트와 Redis TTL을 함께 사용하는 방식은
보안성과 운영 편의성 모두를 고려한 설계라고 자부할 수 있다.
728x90
'BindProject' 카테고리의 다른 글
| [초안] 합주실 탐색 기능의 설계: 정렬에서 표기로, 현실적인 MVP를 위한 선택 (3) | 2025.06.20 |
|---|---|
| [회고] Refresh Token 전략과 Redis 기반 설계, 그리고 트러블슈팅 회고 (1) | 2025.06.20 |
| [기획] 추후 목표 :Kafka 기반 이벤트 아키텍처 이식 설계 (0) | 2025.06.19 |
| [Backend]Auth모듈: 인증 시스템 설계와 구현 -FIN-!! (0) | 2025.06.19 |
| [Backend] Auth모듈 : 유저 탈퇴 처리 (1) | 2025.06.19 |