Update Baron SSO 로그인 방식 및 비표준 인증 흐름 정리.md

This commit is contained in:
2026-06-22 14:06:11 +09:00
parent 680ed343fe
commit f9fdd78d89

View File

@@ -33,6 +33,50 @@ Baron SSO는 Ory Kratos와 Ory Hydra를 기반으로 한다.
참고로 `POST /api/v1/auth/sms`, `POST /api/v1/auth/verify-sms`도 존재하지만, 위의 "표준 2개 + 비표준 3개" 분류에서는 별도 보조 또는 내부 토큰 기반 흐름으로 보는 것이 적절하다. 참고로 `POST /api/v1/auth/sms`, `POST /api/v1/auth/verify-sms`도 존재하지만, 위의 "표준 2개 + 비표준 3개" 분류에서는 별도 보조 또는 내부 토큰 기반 흐름으로 보는 것이 적절하다.
### 2.1 운영 요청 경로와 Gateway 역할
운영 경로에서는 사용자의 요청이 곧바로 Backend, Kratos, Hydra로 들어가지 않는다. 앞단에는 Traefik 또는 외부 L7, Baron Gateway Nginx, Oathkeeper가 있다.
현재 `gateway/nginx.conf` 기준 역할은 다음과 같다.
| 경로 | Nginx 처리 | 다음 대상 |
| --- | --- | --- |
| `/api/*` | Backend API proxy | `baron_backend:3000` |
| `/auth/*` | Ory Stack proxy | `oathkeeper:4455` -> Kratos public |
| `/oidc/*` | Ory Stack proxy | `oathkeeper:4455` -> Hydra public |
| `/` | UserFront 정적/앱 proxy | `baron_userfront:5000` |
그림으로 보면 다음과 같다.
```mermaid
flowchart LR
User[사용자 브라우저]
RP[RP/업무시스템]
L7[Traefik 또는 외부 L7]
GW[Nginx Baron Gateway]
UF[UserFront]
BE[Baron Backend]
OK[Oathkeeper]
KR[Ory Kratos Public]
HY[Ory Hydra Public]
User --> RP
RP -->|OIDC /oidc/oauth2/auth| L7
User -->|Baron SSO 접속| L7
L7 --> GW
GW -->|/| UF
GW -->|/api/v1/*| BE
GW -->|/auth/*| OK
GW -->|/oidc/*| OK
OK -->|Kratos public API| KR
OK -->|Hydra public API| HY
```
따라서 로그인 흐름을 설명할 때는 다음 두 경로를 구분해야 한다.
- 브라우저/API 호출 경로: `사용자 또는 RP -> L7 -> Nginx Gateway -> Backend/Oathkeeper`
- 내부 백채널 경로: `Backend -> Kratos Admin/Public`, `Backend -> Hydra Admin`, `Kratos Courier -> Backend`
## 3. 표준 방식 2개 ## 3. 표준 방식 2개
### 3.1 ID/Password ### 3.1 ID/Password
@@ -49,6 +93,31 @@ Kratos 인증이 성공하면 세션 토큰과 Kratos 세션 쿠키가 발급된
OIDC 로그인 중이면 `login_challenge`도 함께 전달된다. 이 경우 Backend는 Hydra의 login request를 확인한 뒤 `AcceptLoginRequest`를 호출하고, Hydra가 내려준 `redirectTo`를 UserFront에 반환한다. UserFront는 이 URL로 이동하여 RP의 OIDC 흐름을 계속 진행한다. OIDC 로그인 중이면 `login_challenge`도 함께 전달된다. 이 경우 Backend는 Hydra의 login request를 확인한 뒤 `AcceptLoginRequest`를 호출하고, Hydra가 내려준 `redirectTo`를 UserFront에 반환한다. UserFront는 이 URL로 이동하여 RP의 OIDC 흐름을 계속 진행한다.
그림으로 보면 다음과 같다.
```mermaid
sequenceDiagram
autonumber
actor User as 사용자
participant UF as UserFront
participant BE as Baron Backend
participant KR as Ory Kratos
participant HY as Ory Hydra
participant RP as RP/업무시스템
User->>RP: 업무시스템 접속
RP->>HY: OIDC 인증 요청
HY->>UF: login_challenge와 함께 로그인 화면 이동
User->>UF: ID/Password 입력
UF->>BE: POST /api/v1/auth/password/login
BE->>KR: password login flow 제출
KR-->>BE: Kratos session_token, session cookie
BE->>HY: AcceptLoginRequest(login_challenge, subject)
HY-->>BE: redirectTo 반환
BE-->>UF: redirectTo 반환
UF->>RP: OIDC callback으로 이동
```
### 3.2 Login Code ### 3.2 Login Code
Login Code는 Kratos의 `method=code` 기반 로그인이다. 사용자는 이메일 또는 전화번호 등으로 전달받은 코드를 입력하고, Backend는 Kratos login flow에 해당 코드를 제출한다. Login Code는 Kratos의 `method=code` 기반 로그인이다. 사용자는 이메일 또는 전화번호 등으로 전달받은 코드를 입력하고, Backend는 Kratos login flow에 해당 코드를 제출한다.
@@ -61,6 +130,33 @@ Login Code는 Kratos의 `method=code` 기반 로그인이다. 사용자는 이
검증 성공 시 Kratos는 세션 토큰을 발급한다. Baron은 이 세션 토큰을 기준으로 UserFront 세션을 구성하거나, OIDC 흐름이 있으면 Hydra login accept로 이어간다. 검증 성공 시 Kratos는 세션 토큰을 발급한다. Baron은 이 세션 토큰을 기준으로 UserFront 세션을 구성하거나, OIDC 흐름이 있으면 Hydra login accept로 이어간다.
그림으로 보면 다음과 같다.
```mermaid
sequenceDiagram
autonumber
actor User as 사용자
participant UF as UserFront
participant BE as Baron Backend
participant KR as Ory Kratos
participant HY as Ory Hydra
User->>UF: 로그인 ID 입력
UF->>BE: 코드 로그인 시작 요청
BE->>KR: method=code login flow 시작
KR-->>User: 이메일/SMS 등으로 인증 코드 전달
User->>UF: 인증 코드 입력
UF->>BE: POST /api/v1/auth/login/code/verify
BE->>KR: VerifyLoginCode(loginId, flowID, code)
KR-->>BE: Kratos session_token
alt OIDC login_challenge 있음
BE->>HY: AcceptLoginRequest(login_challenge, subject)
HY-->>BE: redirectTo 반환
else 일반 UserFront 로그인
BE-->>UF: sessionJwt 반환
end
```
## 4. 비표준 방식 3개 ## 4. 비표준 방식 3개
비표준 방식은 Kratos/Hydra의 표준 프로토콜만으로 끝나지 않고, Baron Backend가 `pendingRef`, Redis 상태, 폴링, 교차 기기 승인 같은 추가 상태 머신을 운영한다. 비표준 방식은 Kratos/Hydra의 표준 프로토콜만으로 끝나지 않고, Baron Backend가 `pendingRef`, Redis 상태, 폴링, 교차 기기 승인 같은 추가 상태 머신을 운영한다.
@@ -90,6 +186,33 @@ Enchanted Link는 사용자가 로그인 ID를 입력하면 Backend가 로그인
이 방식의 특징은 인증 요청 기기와 승인 기기가 분리될 수 있다는 점이다. 따라서 "누가 요청했는지"와 "누가 승인했는지"를 반드시 분리해서 기록해야 한다. 이 방식의 특징은 인증 요청 기기와 승인 기기가 분리될 수 있다는 점이다. 따라서 "누가 요청했는지"와 "누가 승인했는지"를 반드시 분리해서 기록해야 한다.
그림으로 보면 다음과 같다.
```mermaid
sequenceDiagram
autonumber
actor User as 사용자
participant UF as 요청 브라우저
participant BE as Baron Backend
participant Redis as Redis
participant Approver as 링크/코드 승인 기기
User->>UF: 로그인 ID 입력
UF->>BE: POST /api/v1/auth/enchanted-link/init
BE->>Redis: pendingRef/status=pending 저장
BE-->>User: 로그인 링크 또는 코드 전달
loop 요청 브라우저 폴링
UF->>BE: POST /api/v1/auth/enchanted-link/poll
BE->>Redis: pendingRef 상태 조회
BE-->>UF: authorization_pending
end
User->>Approver: 링크 열기 또는 코드 검증
Approver->>BE: magic-link/code verify
BE->>Redis: pendingRef/status=approved, sessionJwt 저장
UF->>BE: poll 재시도
BE-->>UF: sessionJwt 반환
```
### 4.2 Magic Link Verify ### 4.2 Magic Link Verify
Magic Link Verify는 전달된 토큰을 검증하여 로그인 또는 승인만 수행하는 흐름이다. Magic Link Verify는 전달된 토큰을 검증하여 로그인 또는 승인만 수행하는 흐름이다.
@@ -113,6 +236,31 @@ Magic Link Verify는 전달된 토큰을 검증하여 로그인 또는 승인만
이 방식은 사용자가 링크를 누르는 위치에 따라 동작이 달라질 수 있다. 예를 들어 같은 브라우저에서 바로 로그인할 수도 있고, 다른 기기에서 원래 요청만 승인할 수도 있다. 이 방식은 사용자가 링크를 누르는 위치에 따라 동작이 달라질 수 있다. 예를 들어 같은 브라우저에서 바로 로그인할 수도 있고, 다른 기기에서 원래 요청만 승인할 수도 있다.
그림으로 보면 다음과 같다.
```mermaid
sequenceDiagram
autonumber
actor User as 사용자
participant BE as Baron Backend
participant Redis as Redis
participant UF as UserFront
participant Req as 원래 요청 브라우저
BE->>Redis: token -> loginId/pendingRef 저장
BE-->>User: Magic Link 전달
User->>UF: Magic Link 클릭
UF->>BE: POST /api/v1/auth/magic-link/verify
BE->>Redis: token 조회 및 만료 확인
alt verifyOnly=true
BE->>Redis: 원래 pendingRef 승인 처리
Req->>BE: poll
BE-->>Req: sessionJwt 또는 승인 결과 반환
else 현재 브라우저 로그인
BE-->>UF: token/sessionJwt 반환
end
```
### 4.3 QR 로그인 ### 4.3 QR 로그인
QR 로그인은 요청 기기에서 QR을 표시하고, 이미 로그인된 승인 기기에서 QR을 스캔하여 요청 기기를 로그인시키는 흐름이다. QR 로그인은 요청 기기에서 QR을 표시하고, 이미 로그인된 승인 기기에서 QR을 스캔하여 요청 기기를 로그인시키는 흐름이다.
@@ -139,6 +287,32 @@ QR 로그인은 요청 기기에서 QR을 표시하고, 이미 로그인된 승
QR 로그인은 편의성이 높지만, 타인이 QR을 띄우고 사용자가 실수로 승인하는 상황을 반드시 방어해야 한다. QR 로그인은 편의성이 높지만, 타인이 QR을 띄우고 사용자가 실수로 승인하는 상황을 반드시 방어해야 한다.
그림으로 보면 다음과 같다.
```mermaid
sequenceDiagram
autonumber
participant Web as 요청 기기
participant BE as Baron Backend
participant Redis as Redis
participant Mobile as 승인 기기
Web->>BE: POST /api/v1/auth/qr/init
BE->>Redis: pendingRef/status=pending 저장
BE-->>Web: qrCode, pendingRef 반환
loop QR 상태 폴링
Web->>BE: POST /api/v1/auth/qr/poll
BE->>Redis: pendingRef 상태 조회
BE-->>Web: authorization_pending
end
Mobile->>Web: QR 스캔
Mobile->>BE: POST /api/v1/auth/qr/approve
BE->>BE: 승인 기기의 Kratos 세션 확인
BE->>Redis: pendingRef/status=approved, approverSubject 저장
Web->>BE: poll 재시도
BE-->>Web: sessionJwt 반환
```
## 5. 비표준 방식의 공통 위험 지점 ## 5. 비표준 방식의 공통 위험 지점
비표준 방식의 핵심 위험은 "요청 주체"와 "승인 주체"가 다를 수 있다는 데 있다. 비표준 방식의 핵심 위험은 "요청 주체"와 "승인 주체"가 다를 수 있다는 데 있다.
@@ -390,6 +564,90 @@ Baron SSO의 로그인 방식은 표준 2개와 비표준 3개로 정리할 수
`#1247`, `#1248`, `#1241`에서 제안된 Headless 폰번호 로그인은 다음 흐름을 가진다. `#1247`, `#1248`, `#1241`에서 제안된 Headless 폰번호 로그인은 다음 흐름을 가진다.
전체 흐름을 그림으로 보면 다음과 같다.
```mermaid
sequenceDiagram
autonumber
actor User as 사용자
participant RP as RP/시스템
participant GW as Nginx Gateway
participant BE as Baron Backend
participant Redis as Redis
participant OK as Oathkeeper
participant KR as Ory Kratos
participant HY as Ory Hydra
User->>RP: 전화번호 입력 후 로그인 요청
RP->>GW: POST /api/v1/auth/headless/phone/login
Note over RP,GW: body 예시<br/>client_id=headless-phone-client<br/>phoneNumber=010-1234-5678<br/>login_challenge=abc...<br/>client_assertion=private_key_jwt
GW->>BE: /api 경로를 Backend로 proxy
BE->>BE: RP 서명 검증 및 전화번호 정규화
Note over BE: phoneNumber 정규화<br/>010-1234-5678 -> +821012345678
BE->>Redis: phone pending/interception flag 저장
Note over BE,Redis: key 예시<br/>prefixLoginCodePhonePending:+821012345678<br/>TTL=5m
BE->>KR: InitiateLinkLogin(phoneNumber)
KR-->>BE: flowID 반환
BE->>Redis: phoneNumber -> flowID 저장
Note over BE,Redis: key 예시<br/>prefixLoginCode:+821012345678 -> flowID
KR->>BE: Courier SMS 발송 웹훅 호출
Note over KR,BE: POST /api/v1/auth/webhooks/kratos-courier<br/>recipient=+821012345678<br/>template_data.login_code=123456
BE->>Redis: interception flag 확인
BE->>BE: Courier payload에서 login_code 추출
BE->>KR: VerifyLoginCode(phoneNumber, flowID, login_code)
KR-->>BE: Kratos session_token/session_id 발급
BE->>HY: AcceptLoginRequest(login_challenge, subject)
HY-->>BE: redirectTo 반환
BE-->>GW: redirectTo, sessionId, status 반환
GW-->>RP: JSON 응답 전달
RP->>GW: redirectTo 따라 /oidc/oauth2/auth 계속
GW->>OK: /oidc 경로를 Oathkeeper로 proxy
OK->>HY: strip /oidc 후 Hydra public으로 전달
```
이때 흐름 속 주요 데이터 예시는 다음과 같다.
RP가 Backend로 보내는 요청:
```json
{
"client_id": "headless-phone-client",
"client_assertion": "eyJhbGciOiJSUzI1NiIs...",
"phoneNumber": "010-1234-5678",
"login_challenge": "b59a857064ef4827940bb..."
}
```
Backend가 Redis에 저장하는 임시 키:
```text
prefixLoginCodePhonePending:+821012345678 = interception_flag
prefixLoginCode:+821012345678 = <kratos-flow-id>
TTL = 5m
```
Kratos Courier가 Backend로 보내는 발송 요청의 핵심 데이터:
```json
{
"recipient": "+821012345678",
"template_type": "login_code",
"template_data": {
"login_code": "123456"
}
}
```
Backend가 RP로 반환하는 성공 응답:
```json
{
"status": "ok",
"redirectTo": "https://sso.example.com/oidc/oauth2/auth?login_verifier=...",
"sessionId": "a0f8dc4e-9400-4785-802c-..."
}
```
### 10.1 단계별 데이터 이동 ### 10.1 단계별 데이터 이동
#### Step 1. 인가 및 전화번호 식별 #### Step 1. 인가 및 전화번호 식별