From f9fdd78d8946e74e79b4d36e340f955374a05c6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=ED=98=95=EC=84=9D?= Date: Mon, 22 Jun 2026 14:06:11 +0900 Subject: [PATCH] =?UTF-8?q?Update=20Baron=20SSO=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EB=B0=A9=EC=8B=9D=20=EB=B0=8F=20=EB=B9=84=ED=91=9C?= =?UTF-8?q?=EC=A4=80=20=EC=9D=B8=EC=A6=9D=20=ED=9D=90=EB=A6=84=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...SO 로그인 방식 및 비표준 인증 흐름 정리.md | 258 ++++++++++++++++++ 1 file changed, 258 insertions(+) diff --git a/Baron SSO 로그인 방식 및 비표준 인증 흐름 정리.md b/Baron SSO 로그인 방식 및 비표준 인증 흐름 정리.md index 35e5d5c..4c2b2fa 100644 --- a/Baron SSO 로그인 방식 및 비표준 인증 흐름 정리.md +++ b/Baron SSO 로그인 방식 및 비표준 인증 흐름 정리.md @@ -33,6 +33,50 @@ Baron SSO는 Ory Kratos와 Ory Hydra를 기반으로 한다. 참고로 `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.1 ID/Password @@ -49,6 +93,31 @@ Kratos 인증이 성공하면 세션 토큰과 Kratos 세션 쿠키가 발급된 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 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로 이어간다. +그림으로 보면 다음과 같다. + +```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개 비표준 방식은 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 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 로그인 QR 로그인은 요청 기기에서 QR을 표시하고, 이미 로그인된 승인 기기에서 QR을 스캔하여 요청 기기를 로그인시키는 흐름이다. @@ -139,6 +287,32 @@ 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. 비표준 방식의 공통 위험 지점 비표준 방식의 핵심 위험은 "요청 주체"와 "승인 주체"가 다를 수 있다는 데 있다. @@ -390,6 +564,90 @@ Baron SSO의 로그인 방식은 표준 2개와 비표준 3개로 정리할 수 `#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 예시
client_id=headless-phone-client
phoneNumber=010-1234-5678
login_challenge=abc...
client_assertion=private_key_jwt + GW->>BE: /api 경로를 Backend로 proxy + BE->>BE: RP 서명 검증 및 전화번호 정규화 + Note over BE: phoneNumber 정규화
010-1234-5678 -> +821012345678 + BE->>Redis: phone pending/interception flag 저장 + Note over BE,Redis: key 예시
prefixLoginCodePhonePending:+821012345678
TTL=5m + BE->>KR: InitiateLinkLogin(phoneNumber) + KR-->>BE: flowID 반환 + BE->>Redis: phoneNumber -> flowID 저장 + Note over BE,Redis: key 예시
prefixLoginCode:+821012345678 -> flowID + KR->>BE: Courier SMS 발송 웹훅 호출 + Note over KR,BE: POST /api/v1/auth/webhooks/kratos-courier
recipient=+821012345678
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 = +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 단계별 데이터 이동 #### Step 1. 인가 및 전화번호 식별