BindProject

[Backend] Auth모듈: JWT 로그아웃 설계와 Redis 기반 블랙리스트 구현

dding-shark 2025. 6. 20. 09:20
728x90

1. 도입

JWT 기반 인증 시스템을 운영하면서 가장 흔하게 마주하는 고민 중 하나는 **"로그아웃을 어떻게 처리할 것인가?"**이다.
JWT는 stateless(상태 없음) 구조로 설계되어 있어, 일반적인 세션 기반 시스템처럼 서버에서 상태를 삭제할 수 없기 때문이다.

우리 서비스는 이미 AccessToken / RefreshToken을 분리하여 운영하고 있고,
Redis 기반의 RefreshToken 저장소, Outbox + Kafka 기반의 탈퇴 이벤트 시스템도 갖추고 있다.
이번에는 여기에 로그아웃 처리와 블랙리스트 기반 무효화 기능을 추가하였다.


2. 설계 목표

  • AccessToken의 즉각적 무효화 (만료 시간 이전에도)

  • RefreshToken의 기기별/전체 삭제

  • Redis를 통한 블랙리스트 처리 구현

  • BFF 구조와 역할 분리:

    • BFF는 AccessToken을 파싱해 jti를 넘김
    • Auth는 jti를 Redis에 등록해 토큰을 무효화함

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