@startuml user-로그아웃
!theme mono

title User Service - 로그아웃 내부 시퀀스 (UFR-USER-040)

actor Client
participant "UserController" as Controller <<API Layer>>
participant "AuthenticationService" as AuthService <<Business Layer>>
participant "JwtTokenProvider" as JwtProvider <<Utility>>
participant "Redis\nCache" as Redis <<E>>

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{"code": "AUTH_002",\n"error": "유효하지 않은 토큰입니다"}
    deactivate AuthService
    deactivate Controller

else JWT 토큰 유효

    == 2단계: Redis 세션 삭제 ==

    AuthService -> Redis: 세션 삭제\n(캐시키: 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 확인

    **API Gateway 연계 시나리오**
    1. 로그아웃: AuthService가 Blacklist에 토큰 추가
    2. 후속 API 요청: API Gateway가 Blacklist 확인
       - Redis GET jwt:blacklist:{token}
       - 존재하면: 401 Unauthorized 즉시 반환
       - 존재하지 않으면: 백엔드 서비스로 라우팅
    3. 만료 시간 도달: Redis TTL 만료로 자동 삭제
    end note

    AuthService -> JwtProvider: getRemainingExpiration(token)
    activate JwtProvider
    JwtProvider -> JwtProvider: JWT Claims에서\nexp(만료 시간) 추출\n(현재 시간과 비교)
    JwtProvider --> AuthService: remainingSeconds
    deactivate JwtProvider

    alt 남은 만료 시간 > 0
        AuthService -> Redis: 블랙리스트에 토큰 추가\n(캐시키: jwt:blacklist:{token},\n값: "revoked", TTL: 남은초)
        activate Redis
        Redis --> AuthService: Blacklist 추가 완료
        deactivate Redis
    end

    == 4단계: 로그아웃 이벤트 발행 (선택적) ==

    note right of AuthService
    **로그아웃 로깅 및 이벤트**
    - 감사 로그 기록: userId, timestamp, IP
    - 이벤트 발행: LOGOUT_SUCCESS
    - 분석 데이터 수집: 세션 지속 시간, 활동 통계
    end note

    AuthService -> AuthService: 로그아웃 성공 로그 기록\n(userId, timestamp, sessionDuration)

    AuthService ->> AuthService: publishEvent(LOGOUT_SUCCESS)
    note right of AuthService
    **이벤트 활용**
    - 비동기 이벤트 처리
    - 분석 시스템 연동
    - 감사 로그 저장소 전송
    end note

    == 5단계: 응답 반환 ==

    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에서 세션 정보 제거
- 감사 로그: userId, timestamp, IP, sessionDuration 기록

**API Gateway 연계**
- Blacklist 확인: GET jwt:blacklist:{token}
- 존재 시: 401 Unauthorized 즉시 반환
- TTL 자동 관리: 만료 시간 도달 시 자동 삭제

**클라이언트 측 처리**
- 프론트엔드: LocalStorage 또는 Cookie에서 JWT 토큰 삭제
- 로그인 화면으로 리다이렉트
- 모든 인증 헤더 제거

**성능 목표**
- Redis 삭제 연산: O(1) 시간 복잡도
- 평균 응답 시간: 0.1초 이내
- P95 응답 시간: 0.2초 이내

**이벤트 처리**
- LOGOUT_SUCCESS 이벤트 발행 (비동기)
- 감사 로그 저장소 전송
- 분석 데이터 수집

**에러 코드**
- AUTH_002: JWT 토큰 무효
end note

@enduml
