@startuml user-로그아웃 !theme mono title User Service - 로그아웃 내부 시퀀스 (UFR-USER-040) actor Client participant "UserController" as Controller <> participant "AuthenticationService" as AuthService <> participant "JwtTokenProvider" as JwtProvider <> participant "Redis\nCache" as Redis <> note over Controller, Redis **UFR-USER-040: 로그아웃** - JWT 토큰 검증 - Redis 세션 삭제 - 클라이언트 측 토큰 삭제 (프론트엔드 처리) end note Client -> Controller: POST /api/users/logout\nAuthorization: Bearer {JWT} activate Controller Controller -> Controller: @AuthenticationPrincipal\n(JWT에서 userId 추출) Controller -> Controller: JWT 토큰 추출\n(Authorization 헤더에서) Controller -> AuthService: logout(token, userId) activate AuthService == 1단계: JWT 토큰 검증 == AuthService -> JwtProvider: validateToken(token) activate JwtProvider JwtProvider -> JwtProvider: JWT 서명 검증\n(만료 시간 확인) JwtProvider --> AuthService: boolean (유효 여부) deactivate JwtProvider alt JWT 토큰 무효 AuthService --> Controller: throw InvalidTokenException\n("유효하지 않은 토큰입니다") Controller --> Client: 401 Unauthorized\n{"error": "유효하지 않은 토큰입니다"} deactivate AuthService deactivate Controller else JWT 토큰 유효 == 2단계: Redis 세션 삭제 == AuthService -> Redis: DEL user:session:{token} activate Redis Redis --> AuthService: 삭제된 키 개수 (0 또는 1) deactivate Redis alt 세션 없음 (이미 로그아웃됨) note right of AuthService **멱등성 보장** - 세션이 없어도 로그아웃 성공으로 처리 - 중복 로그아웃 요청에 안전 end note else 세션 있음 (정상 로그아웃) note right of AuthService **세션 삭제 완료** - Redis에서 세션 정보 제거 - JWT 토큰 무효화 (Blacklist 방식) end note end == 3단계: JWT 토큰 Blacklist 추가 (선택적) == note right of AuthService **JWT Blacklist 전략** - 만료되지 않은 JWT 토큰을 강제로 무효화 - Redis에 토큰을 Blacklist에 추가 (TTL: 남은 만료 시간) - API Gateway에서 Blacklist 확인 end note AuthService -> JwtProvider: getRemainingExpiration(token) activate JwtProvider JwtProvider -> JwtProvider: JWT Claims에서\nexp(만료 시간) 추출\n(현재 시간과 비교) JwtProvider --> AuthService: remainingSeconds deactivate JwtProvider alt 남은 만료 시간 > 0 AuthService -> Redis: SET jwt:blacklist:{token}\n"revoked" (TTL: remainingSeconds) activate Redis Redis --> AuthService: Blacklist 추가 완료 deactivate Redis end == 4단계: 응답 반환 == AuthService -> AuthService: 로그아웃 성공 로그 기록\n(userId, timestamp) AuthService --> Controller: LogoutResponse\n(success: true) deactivate AuthService Controller --> Client: 200 OK\n{"success": true,\n"message": "안전하게 로그아웃되었습니다"} deactivate Controller end note over Controller, Redis **보안 처리** - JWT 토큰 Blacklist: 만료 전 토큰 강제 무효화 - 멱등성 보장: 중복 로그아웃 요청에 안전 - 세션 완전 삭제: Redis에서 세션 정보 제거 **클라이언트 측 처리** - 프론트엔드: LocalStorage 또는 Cookie에서 JWT 토큰 삭제 - 로그인 화면으로 리다이렉트 **성능 최적화** - Redis 삭제 연산: O(1) 시간 복잡도 - 응답 시간: 0.1초 이내 end note @enduml