@startuml !theme mono title 사용자 인증 내부 시퀀스 (AFR-USER-010) participant "API Gateway<>" as Gateway participant "UserController" as Controller participant "AuthService" as Service participant "LdapAuthenticator" as LdapAuth participant "LDAP<>" as LDAP participant "JwtTokenProvider" as TokenProvider participant "UserRepository" as Repository database "PostgreSQL<>" as DB database "Redis Cache<>" as Cache Gateway -> Controller: POST /api/v1/auth/login\n{username, password} activate Controller Controller -> Service: authenticate(username, password) activate Service Service -> LdapAuth: validateCredentials(username, password) activate LdapAuth LdapAuth -> LDAP: bind(dn, password) note right LDAP 인증: - DN: cn={username},ou=users,dc=company,dc=com - Protocol: LDAPS (636) - Timeout: 5s end note alt 인증 성공 LDAP --> LdapAuth: authentication success LdapAuth -> LDAP: searchUser(username) note right 사용자 정보 조회: - cn (이름) - mail (이메일) - department (부서) - title (직급) end note LDAP --> LdapAuth: user attributes LdapAuth --> Service: UserDetails deactivate LdapAuth Service -> Repository: findByUsername(username) activate Repository Repository -> DB: 사용자 정보 조회\n(사용자명 기준) alt 사용자 존재 DB --> Repository: user data else 신규 사용자 Repository -> DB: 신규 사용자 등록\n(사용자명, 이메일, 부서) DB --> Repository: user created note right LDAP 정보 동기화: - 자동 사용자 등록 - 프로필 정보 저장 end note end Repository --> Service: User entity deactivate Repository Service -> TokenProvider: generateAccessToken(user) activate TokenProvider TokenProvider -> TokenProvider: createClaims(user) note right JWT Claims: - sub: userId - username - roles - exp: 1h end note TokenProvider -> TokenProvider: signToken(claims, secretKey) TokenProvider --> Service: access token deactivate TokenProvider Service -> TokenProvider: generateRefreshToken(user) activate TokenProvider TokenProvider -> TokenProvider: createRefreshClaims(user) note right Refresh Token: - sub: userId - type: refresh - exp: 7d end note TokenProvider --> Service: refresh token deactivate TokenProvider Service -> Cache: storeRefreshToken(userId, refreshToken) note right Redis 저장: - Key: refresh:{userId} - Value: refreshToken - TTL: 7d end note Cache --> Service: stored Service -> Repository: updateLastLogin(userId) activate Repository Repository -> DB: 최종 로그인 일시 업데이트\n(현재시각) DB --> Repository: updated Repository --> Service: updated deactivate Repository Service --> Controller: AuthResponse\n{accessToken, refreshToken, user} deactivate Service Controller --> Gateway: 200 OK\n{tokens, userInfo} deactivate Controller else 인증 실패 LDAP --> LdapAuth: authentication failed LdapAuth --> Service: AuthenticationException deactivate LdapAuth Service -> Repository: incrementFailedAttempts(username) activate Repository Repository -> DB: 실패 횟수 증가\n(failed_attempts + 1) alt 실패 횟수 초과 (5회) Repository -> DB: 계정 잠금 설정\n(잠금시간=현재+30분) note right 계정 잠금: - 5회 실패 시 - 30분 잠금 end note end DB --> Repository: updated Repository --> Service: updated deactivate Repository Service --> Controller: AuthenticationException deactivate Service Controller --> Gateway: 401 Unauthorized note right 에러 응답 형식: { "error": { "code": "AUTHENTICATION_FAILED", "message": "인증에 실패했습니다", "details": "사용자명 또는 비밀번호가 올바르지 않습니다", "timestamp": "2025-10-23T12:00:00Z", "path": "/api/v1/auth/login" } } end note deactivate Controller end @enduml