@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: SELECT * FROM users\nWHERE username = ? alt 사용자 존재 DB --> Repository: user data else 신규 사용자 Repository -> DB: INSERT INTO users\n(username, email, department) 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: UPDATE users\nSET last_login_at = NOW() 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: UPDATE users\nSET failed_attempts = failed_attempts + 1 alt 실패 횟수 초과 (5회) Repository -> DB: UPDATE users\nSET locked_until = NOW() + INTERVAL '30 minutes' note right 계정 잠금: - 5회 실패 시 - 30분 잠금 end note end DB --> Repository: updated Repository --> Service: updated deactivate Repository Service --> Controller: AuthenticationException deactivate Service Controller --> Gateway: 401 Unauthorized\n{error: "Invalid credentials"} deactivate Controller end @enduml