Add Baron SSO 로그인 방식 및 비표준 인증 흐름 정리.md
This commit is contained in:
525
Baron SSO 로그인 방식 및 비표준 인증 흐름 정리.md
Normal file
525
Baron SSO 로그인 방식 및 비표준 인증 흐름 정리.md
Normal file
@@ -0,0 +1,525 @@
|
||||
# Baron SSO 로그인 방식 및 비표준 인증 흐름 정리
|
||||
|
||||
작성일: 2026-06-22
|
||||
|
||||
## 1. 목적
|
||||
|
||||
본 문서는 Baron SSO의 로그인 방식을 표준 방식과 비표준 방식으로 구분하고, 특히 비표준 방식에서 데이터와 인증 상태가 어떻게 이동하는지 설명하기 위해 작성한다.
|
||||
|
||||
팀장 요청사항은 다음 세 가지로 정리된다.
|
||||
|
||||
- 표준 로그인 방식에 대한 이해
|
||||
- 비표준 로그인 방식에서 데이터와 인증 흐름에 대한 이해 및 설명
|
||||
- 사용자가 아닌 사람의 로그인 요청이 발견될 때 사용자에게 알리고, 차단 또는 비차단 요구를 받아 단계적으로 처리하는 방안 검토
|
||||
|
||||
## 2. 현재 로그인 방식 요약
|
||||
|
||||
Baron SSO는 Ory Kratos와 Ory Hydra를 기반으로 한다.
|
||||
|
||||
- Kratos: 사용자 인증, 세션 발급, 비밀번호 및 코드 로그인 처리
|
||||
- Hydra: OIDC/OAuth2 인가 서버, RP 로그인 요청과 consent 처리
|
||||
- UserFront: 사용자 로그인 UI
|
||||
- Backend: UserFront와 Ory 사이의 중계 및 Baron 커스텀 로그인 흐름 처리
|
||||
|
||||
현재 사용자 로그인 방식은 아래처럼 5개로 볼 수 있다.
|
||||
|
||||
| 구분 | 방식 | 대표 엔드포인트 | 성격 |
|
||||
| --- | --- | --- | --- |
|
||||
| 표준 | ID/Password | `POST /api/v1/auth/password/login` | Kratos password login |
|
||||
| 표준 | Login Code | `POST /api/v1/auth/login/code/verify` | Kratos code login |
|
||||
| 비표준 | Enchanted Link | `POST /api/v1/auth/enchanted-link/init` -> `poll` | Baron pendingRef 기반 링크 승인 |
|
||||
| 비표준 | Magic Link Verify | `POST /api/v1/auth/magic-link/verify` | Baron token 검증 및 verify-only |
|
||||
| 비표준 | QR 로그인 | `POST /api/v1/auth/qr/init` -> `poll` -> `approve` | Baron 교차 기기 승인 |
|
||||
|
||||
참고로 `POST /api/v1/auth/sms`, `POST /api/v1/auth/verify-sms`도 존재하지만, 위의 "표준 2개 + 비표준 3개" 분류에서는 별도 보조 또는 내부 토큰 기반 흐름으로 보는 것이 적절하다.
|
||||
|
||||
## 3. 표준 방식 2개
|
||||
|
||||
### 3.1 ID/Password
|
||||
|
||||
사용자가 로그인 ID와 비밀번호를 입력하면 UserFront가 Backend의 `POST /api/v1/auth/password/login`을 호출한다.
|
||||
|
||||
Backend는 Kratos Public API에서 login flow를 만들고, Kratos에 다음 정보를 제출한다.
|
||||
|
||||
- `identifier`: 로그인 ID
|
||||
- `password`: 비밀번호
|
||||
- `method`: `password`
|
||||
|
||||
Kratos 인증이 성공하면 세션 토큰과 Kratos 세션 쿠키가 발급된다. Baron Backend는 Kratos identity ID를 Baron의 subject로 해석하고, 사용자 상태가 로그인 가능한 상태인지 확인한다.
|
||||
|
||||
OIDC 로그인 중이면 `login_challenge`도 함께 전달된다. 이 경우 Backend는 Hydra의 login request를 확인한 뒤 `AcceptLoginRequest`를 호출하고, Hydra가 내려준 `redirectTo`를 UserFront에 반환한다. UserFront는 이 URL로 이동하여 RP의 OIDC 흐름을 계속 진행한다.
|
||||
|
||||
### 3.2 Login Code
|
||||
|
||||
Login Code는 Kratos의 `method=code` 기반 로그인이다. 사용자는 이메일 또는 전화번호 등으로 전달받은 코드를 입력하고, Backend는 Kratos login flow에 해당 코드를 제출한다.
|
||||
|
||||
핵심 데이터는 다음과 같다.
|
||||
|
||||
- `loginId`: 코드가 발급된 사용자 식별자
|
||||
- `flowID`: Kratos login flow ID
|
||||
- `code`: 사용자가 제출한 인증 코드
|
||||
|
||||
검증 성공 시 Kratos는 세션 토큰을 발급한다. Baron은 이 세션 토큰을 기준으로 UserFront 세션을 구성하거나, OIDC 흐름이 있으면 Hydra login accept로 이어간다.
|
||||
|
||||
## 4. 비표준 방식 3개
|
||||
|
||||
비표준 방식은 Kratos/Hydra의 표준 프로토콜만으로 끝나지 않고, Baron Backend가 `pendingRef`, Redis 상태, 폴링, 교차 기기 승인 같은 추가 상태 머신을 운영한다.
|
||||
|
||||
### 4.1 Enchanted Link
|
||||
|
||||
Enchanted Link는 사용자가 로그인 ID를 입력하면 Backend가 로그인 대기 상태를 만들고, 링크 또는 코드 기반 승인을 기다리는 방식이다.
|
||||
|
||||
대표 흐름은 다음과 같다.
|
||||
|
||||
1. UserFront가 `POST /api/v1/auth/enchanted-link/init` 호출
|
||||
2. Backend가 사용자 존재 여부를 확인
|
||||
3. Backend가 Redis에 `pendingRef` 기준 대기 상태 저장
|
||||
4. 사용자에게 링크 또는 코드 전달
|
||||
5. UserFront는 `POST /api/v1/auth/enchanted-link/poll`로 상태 폴링
|
||||
6. 사용자가 링크를 열거나 코드를 검증하면 대기 상태가 승인됨
|
||||
7. 폴링 중인 원래 브라우저가 `sessionJwt`를 받아 로그인 완료
|
||||
|
||||
주요 데이터는 다음과 같다.
|
||||
|
||||
| 데이터 | 의미 |
|
||||
| --- | --- |
|
||||
| `pendingRef` | 로그인 대기 요청의 임시 참조 키 |
|
||||
| `loginId` | 로그인 대상 사용자 |
|
||||
| `status` | `pending`, `approved`, `success`, `expired` 등 |
|
||||
| `sessionJwt` | 승인 후 발급되는 세션 토큰 |
|
||||
|
||||
이 방식의 특징은 인증 요청 기기와 승인 기기가 분리될 수 있다는 점이다. 따라서 "누가 요청했는지"와 "누가 승인했는지"를 반드시 분리해서 기록해야 한다.
|
||||
|
||||
### 4.2 Magic Link Verify
|
||||
|
||||
Magic Link Verify는 전달된 토큰을 검증하여 로그인 또는 승인만 수행하는 흐름이다.
|
||||
|
||||
대표 흐름은 다음과 같다.
|
||||
|
||||
1. Backend가 magic token을 발급하고 Redis에 저장
|
||||
2. 사용자가 링크를 클릭
|
||||
3. UserFront가 `POST /api/v1/auth/magic-link/verify` 호출
|
||||
4. Backend가 token을 조회하고 만료 여부 확인
|
||||
5. `verifyOnly` 여부에 따라 즉시 세션 발급 또는 원래 pending 요청 승인
|
||||
|
||||
주요 데이터는 다음과 같다.
|
||||
|
||||
| 데이터 | 의미 |
|
||||
| --- | --- |
|
||||
| `token` | 링크에 포함된 일회성 검증 토큰 |
|
||||
| `pendingRef` | 원래 로그인 요청이 있는 경우 연결되는 대기 키 |
|
||||
| `verifyOnly` | 현재 창에서 로그인하지 않고 원래 요청만 승인할지 여부 |
|
||||
| `approverSubject` | 승인을 수행한 사용자 identity |
|
||||
|
||||
이 방식은 사용자가 링크를 누르는 위치에 따라 동작이 달라질 수 있다. 예를 들어 같은 브라우저에서 바로 로그인할 수도 있고, 다른 기기에서 원래 요청만 승인할 수도 있다.
|
||||
|
||||
### 4.3 QR 로그인
|
||||
|
||||
QR 로그인은 요청 기기에서 QR을 표시하고, 이미 로그인된 승인 기기에서 QR을 스캔하여 요청 기기를 로그인시키는 흐름이다.
|
||||
|
||||
대표 흐름은 다음과 같다.
|
||||
|
||||
1. 요청 기기에서 UserFront가 `POST /api/v1/auth/qr/init` 호출
|
||||
2. Backend가 `pendingRef`와 QR payload 생성
|
||||
3. 요청 기기는 QR을 표시하고 `POST /api/v1/auth/qr/poll`로 상태 폴링
|
||||
4. 승인 기기는 QR을 스캔하고 `POST /api/v1/auth/qr/approve` 호출
|
||||
5. Backend가 승인 기기의 현재 세션을 확인
|
||||
6. 승인된 subject를 기준으로 pending 상태를 승인 처리
|
||||
7. 요청 기기의 poll 응답으로 `sessionJwt`가 내려가고 로그인 완료
|
||||
|
||||
주요 데이터는 다음과 같다.
|
||||
|
||||
| 데이터 | 의미 |
|
||||
| --- | --- |
|
||||
| `pendingRef` | QR 로그인 요청 식별자 |
|
||||
| `qrCode` | 요청 기기에 표시되는 QR 데이터 |
|
||||
| `approverSessionID` | QR을 승인한 기기의 세션 ID |
|
||||
| `approverSubject` | QR을 승인한 사용자 |
|
||||
| `requestDevice` | QR을 띄운 기기의 IP, User-Agent, 대략적 위치 |
|
||||
|
||||
QR 로그인은 편의성이 높지만, 타인이 QR을 띄우고 사용자가 실수로 승인하는 상황을 반드시 방어해야 한다.
|
||||
|
||||
## 5. 비표준 방식의 공통 위험 지점
|
||||
|
||||
비표준 방식의 핵심 위험은 "요청 주체"와 "승인 주체"가 다를 수 있다는 데 있다.
|
||||
|
||||
예를 들어 공격자가 자기 PC에서 로그인 링크 또는 QR 요청을 만들고, 실제 사용자에게 승인 행동을 유도할 수 있다. 이때 사용자가 아무 정보 없이 승인하면 공격자의 기기가 로그인될 수 있다.
|
||||
|
||||
따라서 비표준 방식에는 다음 데이터가 필수로 남아야 한다.
|
||||
|
||||
- 요청 생성 시각
|
||||
- 요청 만료 시각
|
||||
- 요청 IP
|
||||
- 요청 User-Agent
|
||||
- 요청 기기 유형
|
||||
- 요청 위치 추정값
|
||||
- 요청 RP 또는 client ID
|
||||
- 승인 사용자 subject
|
||||
- 승인 세션 ID
|
||||
- 승인 기기 IP/User-Agent
|
||||
- 승인 또는 거절 결과
|
||||
|
||||
## 6. 사용자가 아닌 사람의 로그인 요청 발견 시 처리 방안
|
||||
|
||||
### 6.1 기본 원칙
|
||||
|
||||
타인의 로그인 요청 가능성이 있는 경우, 시스템은 사용자가 판단할 수 있는 정보를 보여주고 명확한 선택지를 제공해야 한다.
|
||||
|
||||
사용자에게 제공할 선택지는 최소 3개가 적절하다.
|
||||
|
||||
| 사용자 선택 | 의미 | 처리 |
|
||||
| --- | --- | --- |
|
||||
| 내 요청입니다 | 사용자가 요청을 인지하고 승인 | 제한 조건 검증 후 승인 |
|
||||
| 내 요청이 아닙니다 | 타인의 요청으로 판단 | 즉시 차단, pending 폐기, 보안 이벤트 기록 |
|
||||
| 잘 모르겠습니다 | 확신 불가 | 기본 차단 또는 보류, 추가 확인 유도 |
|
||||
|
||||
보안 관점의 기본값은 "비차단"이 아니라 "보류 또는 차단"이어야 한다.
|
||||
|
||||
### 6.2 사용자 알림 방식
|
||||
|
||||
의심 요청이 생성되거나 승인 직전에 다음 정보를 사용자에게 보여준다.
|
||||
|
||||
- 요청 서비스: 예) Baron SSO, DevFront, AdminFront, 특정 RP
|
||||
- 요청 시간
|
||||
- 요청 기기: 브라우저, OS, 모바일/데스크톱 추정
|
||||
- 요청 위치: 국가/지역 수준의 추정값
|
||||
- 요청 IP 일부 마스킹
|
||||
- 승인 시 결과: "이 요청을 승인하면 해당 기기가 로그인됩니다."
|
||||
|
||||
알림 채널은 상황에 따라 다르게 적용한다.
|
||||
|
||||
| 상황 | 권장 알림 |
|
||||
| --- | --- |
|
||||
| QR 승인 화면 | 승인 화면에 요청 정보 직접 표시 |
|
||||
| Magic Link 클릭 | 링크 검증 화면에 요청 정보 표시 |
|
||||
| Enchanted Link 승인 | 승인 화면 또는 poll 연결 화면에 요청 정보 표시 |
|
||||
| 고위험 요청 | 이메일/SMS/앱 알림 병행 |
|
||||
|
||||
### 6.3 단계별 처리 흐름
|
||||
|
||||
#### 1단계: 요청 생성
|
||||
|
||||
Backend가 pending 요청을 만들 때 요청 메타데이터를 함께 저장한다.
|
||||
|
||||
저장 예시는 다음과 같다.
|
||||
|
||||
```json
|
||||
{
|
||||
"pendingRef": "abc123",
|
||||
"loginId": "user@example.com",
|
||||
"requestIp": "203.0.113.xxx",
|
||||
"requestUserAgent": "Chrome on Windows",
|
||||
"requestDeviceType": "desktop",
|
||||
"requestClientId": "userfront",
|
||||
"requestedAt": "2026-06-22T10:00:00+09:00",
|
||||
"expiresAt": "2026-06-22T10:05:00+09:00",
|
||||
"status": "pending"
|
||||
}
|
||||
```
|
||||
|
||||
#### 2단계: 위험도 평가
|
||||
|
||||
요청 생성 시 또는 승인 직전에 risk score를 계산한다.
|
||||
|
||||
위험도를 높이는 조건은 다음과 같다.
|
||||
|
||||
- 최근 로그인 위치와 크게 다른 위치
|
||||
- 새 기기 또는 새 브라우저
|
||||
- 짧은 시간 내 반복 요청
|
||||
- 실패한 비밀번호 시도 직후 링크/QR 요청
|
||||
- 관리자 계정 또는 권한 높은 계정
|
||||
- 비활성/잠금/퇴사 상태 사용자
|
||||
- RP client가 inactive 또는 미승인 상태
|
||||
|
||||
#### 3단계: 사용자 확인
|
||||
|
||||
승인 화면에서 다음 문구를 제공한다.
|
||||
|
||||
```text
|
||||
새 로그인 요청이 감지되었습니다.
|
||||
|
||||
요청 기기: Chrome on Windows
|
||||
요청 위치: Seoul, KR
|
||||
요청 시간: 2026-06-22 10:00
|
||||
요청 서비스: Baron SSO
|
||||
|
||||
이 요청을 승인하면 해당 기기가 로그인됩니다.
|
||||
본인이 요청한 로그인이 맞습니까?
|
||||
```
|
||||
|
||||
버튼은 다음처럼 구성한다.
|
||||
|
||||
- `내 요청입니다`
|
||||
- `내 요청이 아닙니다`
|
||||
- `잘 모르겠습니다`
|
||||
|
||||
#### 4단계: 차단 선택 처리
|
||||
|
||||
사용자가 `내 요청이 아닙니다`를 선택하면 다음 순서로 처리한다.
|
||||
|
||||
1. `pendingRef` 상태를 `blocked`로 변경
|
||||
2. 해당 pending 요청에서 세션 발급 금지
|
||||
3. poll 중인 요청 기기에는 `blocked_by_user` 응답 반환
|
||||
4. 감사 로그에 `login.request.blocked_by_user` 기록
|
||||
5. 사용자에게 비밀번호 변경 또는 전체 세션 로그아웃 안내
|
||||
6. 반복 발생 시 계정 보호 모드 전환 검토
|
||||
|
||||
요청 기기에는 구체적인 사용자 정보를 노출하지 않는다. 예시는 다음 정도가 적절하다.
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "login_request_blocked",
|
||||
"code": "blocked_by_user",
|
||||
"message": "This login request was not approved."
|
||||
}
|
||||
```
|
||||
|
||||
#### 5단계: 비차단 선택 처리
|
||||
|
||||
사용자가 `내 요청입니다`를 선택하면 바로 승인하지 않고 최소 검증을 한 번 더 수행한다.
|
||||
|
||||
검증 항목은 다음과 같다.
|
||||
|
||||
- pending 요청이 만료되지 않았는지
|
||||
- 승인 사용자의 subject와 로그인 대상 subject가 같은지
|
||||
- 기존 브라우저 세션과 대상 subject가 충돌하지 않는지
|
||||
- RP client가 활성 상태인지
|
||||
- 사용자 상태가 로그인 가능 상태인지
|
||||
|
||||
검증 통과 후에만 세션 발급 또는 Hydra `AcceptLoginRequest`를 수행한다.
|
||||
|
||||
#### 6단계: 보류 선택 처리
|
||||
|
||||
사용자가 `잘 모르겠습니다`를 선택하면 기본적으로 승인하지 않는다.
|
||||
|
||||
처리 방식은 다음 중 하나가 적절하다.
|
||||
|
||||
- 보안 우선: 즉시 `blocked_uncertain` 처리
|
||||
- UX 우선: `hold` 상태로 바꾸고 짧은 시간 뒤 만료
|
||||
|
||||
권장안은 보안 우선이다. 사용자가 모르는 요청은 사실상 승인하면 안 된다.
|
||||
|
||||
## 7. 구현 검토 항목
|
||||
|
||||
### 7.1 데이터 모델 또는 Redis 상태 확장
|
||||
|
||||
비표준 로그인 pending 상태에 아래 필드를 공통으로 포함하는 것이 좋다.
|
||||
|
||||
- `requestIp`
|
||||
- `requestUserAgent`
|
||||
- `requestDeviceType`
|
||||
- `requestGeo`
|
||||
- `requestClientId`
|
||||
- `requestedAt`
|
||||
- `expiresAt`
|
||||
- `riskLevel`
|
||||
- `riskReasons`
|
||||
- `approvalDecision`
|
||||
- `approvalSubject`
|
||||
- `approvalSessionID`
|
||||
- `approvalIp`
|
||||
- `approvalUserAgent`
|
||||
|
||||
### 7.2 API 응답 표준화
|
||||
|
||||
poll 계열 API는 상태를 명확히 분리해야 한다.
|
||||
|
||||
| 상태 | 의미 |
|
||||
| --- | --- |
|
||||
| `authorization_pending` | 아직 승인 전 |
|
||||
| `approved` | 승인됨, 세션 발급 가능 |
|
||||
| `blocked_by_user` | 사용자가 차단 |
|
||||
| `blocked_by_policy` | 정책상 차단 |
|
||||
| `expired_token` | 만료 |
|
||||
| `slow_down` | 폴링 속도 제한 |
|
||||
|
||||
### 7.3 감사 로그
|
||||
|
||||
최소 이벤트는 다음과 같다.
|
||||
|
||||
- `login.request.created`
|
||||
- `login.request.viewed_by_user`
|
||||
- `login.request.approved_by_user`
|
||||
- `login.request.blocked_by_user`
|
||||
- `login.request.blocked_by_policy`
|
||||
- `login.request.expired`
|
||||
- `login.session.issued`
|
||||
|
||||
### 7.4 사용자 화면
|
||||
|
||||
QR, Magic Link, Enchanted Link 승인 화면은 공통 컴포넌트를 사용할 수 있다.
|
||||
|
||||
공통 컴포넌트가 표시해야 할 정보는 다음과 같다.
|
||||
|
||||
- 요청 서비스
|
||||
- 요청 기기
|
||||
- 요청 위치
|
||||
- 요청 시간
|
||||
- 승인 결과 설명
|
||||
- 차단/승인/보류 버튼
|
||||
|
||||
## 8. 결론
|
||||
|
||||
Baron SSO의 로그인 방식은 표준 2개와 비표준 3개로 정리할 수 있다.
|
||||
|
||||
- 표준 방식은 Kratos의 password/code login을 기반으로 하므로 이해 지점은 Kratos flow와 세션 발급이다.
|
||||
- 비표준 방식은 Baron이 pendingRef, Redis 상태, 폴링, 교차 기기 승인을 추가한 구조이므로 요청자와 승인자를 분리해서 이해해야 한다.
|
||||
- 사용자가 아닌 사람의 로그인 요청을 막으려면 승인 직전에 요청 메타데이터를 사용자에게 보여주고, `내 요청입니다`, `내 요청이 아닙니다`, `잘 모르겠습니다` 선택을 받아 상태 머신으로 처리해야 한다.
|
||||
|
||||
가장 중요한 정책은 다음 한 문장으로 요약된다.
|
||||
|
||||
> 사용자가 명확히 본인 요청이라고 확인하지 않은 비표준 로그인 요청은 세션 발급으로 이어지면 안 된다.
|
||||
|
||||
## 9. 최근 운영 이슈와의 연관성
|
||||
|
||||
최근 운영 저장소 이슈 중 로그인 방식 검토와 직접 연결되는 항목은 다음과 같다.
|
||||
|
||||
| 이슈 | 연관도 | 검토 포인트 |
|
||||
| --- | --- | --- |
|
||||
| `#1249` Naver Works 버튼 기반 로그아웃 요청 | 매우 높음 | 로그인 성공 알림 후 사용자가 "내가 아닌 로그인"을 선택하면 refresh grant revoke로 세션을 끊는 구조이다. 본 문서의 차단 처리 흐름과 직접 연결된다. |
|
||||
| `#1248` 휴대폰 번호 입력만으로 즉시 로그인 | 매우 높음 | 전화번호 하나만으로 세션을 만드는 설계이다. Kratos code flow를 활용하더라도 SMS 발송과 사용자 코드 입력을 생략하므로 고위험 비표준 흐름으로 분류해야 한다. |
|
||||
| `#1247` Baron Backend와 Ory Kratos 인터랙션 분석 | 매우 높음 | Headless 방식, PKCE 방식, Courier 인터셉트 패턴을 설명한다. 표준/비표준 경계와 데이터 흐름 검토의 핵심 자료이다. |
|
||||
| `#1246` 로그아웃 처리 방식 refresh revoke 전환 | 높음 | 타인 로그인 또는 의심 로그인 발견 후 세션을 종료하는 후속 조치와 연결된다. polling/Redis 기반보다 refresh revoke 기반이 최신 방향으로 보인다. |
|
||||
| `#1245` QR/링크 로그인 분석 및 Headless 폰번호 적용 | 매우 높음 | 기존 비표준 방식인 QR/링크 흐름을 Headless 폰번호 로그인에 재사용하려는 설계이다. |
|
||||
| `#1241` Headless 폰번호 전용 로그인 API | 매우 높음 | `client_assertion`, `phoneNumber`, `login_challenge`만으로 OIDC 로그인을 완료하는 API이다. 사용자 실재 인증 여부를 별도로 검토해야 한다. |
|
||||
| `#1242` 세션 폐기 Redis 버퍼링 | 중간 | 세션 폐기 이벤트를 RP가 감지하게 하는 구조이나, `#1246`의 refresh revoke 방식과 정책 충돌 여부를 확인해야 한다. |
|
||||
| `#1244` DevFront 로그인 후 언어 전환 이슈 | 낮음 | 로그인 이후 UX 문제이며 인증 흐름 자체와 직접 관련은 낮다. |
|
||||
| `#1250` 인력정보 백업 및 스테이징 복구 | 중간 | Kratos identity 포함 여부와 credential/session reset policy가 로그인 데이터 관리와 간접 연결된다. |
|
||||
|
||||
## 10. Headless 폰번호 로그인 추가 검토
|
||||
|
||||
`#1247`, `#1248`, `#1241`에서 제안된 Headless 폰번호 로그인은 다음 흐름을 가진다.
|
||||
|
||||
### 10.1 단계별 데이터 이동
|
||||
|
||||
#### Step 1. 인가 및 전화번호 식별
|
||||
|
||||
RP가 Baron Backend에 다음 데이터를 전달한다.
|
||||
|
||||
- `client_id`
|
||||
- `client_assertion`: RP의 private key JWT 서명값
|
||||
- `phoneNumber`: 사용자가 입력한 전화번호
|
||||
- `login_challenge`: Hydra OIDC 로그인 요청 식별자
|
||||
|
||||
Backend는 먼저 RP가 폰번호 전용 로그인을 사용할 수 있는 기등록 클라이언트인지 검증한다. 이후 전화번호를 E.164 형식으로 정규화한다.
|
||||
|
||||
예시:
|
||||
|
||||
```text
|
||||
010-1234-5678 -> +821012345678
|
||||
```
|
||||
|
||||
그 다음 정규화된 전화번호가 Baron/Kratos 원장에 존재하는지 확인한다.
|
||||
|
||||
#### Step 2. 인터셉터 플래그 캐싱
|
||||
|
||||
Backend는 Redis에 인증 발송 차단 또는 내부 처리용 플래그를 저장한다.
|
||||
|
||||
```text
|
||||
prefixLoginCodePhonePending:+821012345678 -> interception_flag
|
||||
TTL: 5m
|
||||
```
|
||||
|
||||
이 플래그는 Kratos가 Courier를 통해 SMS 발송을 시도할 때, 실제 외부 SMS 발송으로 보내지 않고 Backend 내부 처리 흐름으로 분기시키기 위한 신호 역할을 한다.
|
||||
|
||||
#### Step 3. 대리 인증 흐름 기동
|
||||
|
||||
Backend는 Kratos code login flow를 시작한다.
|
||||
|
||||
```text
|
||||
h.IdpProvider.InitiateLinkLogin("+821012345678", returnTo)
|
||||
```
|
||||
|
||||
Kratos는 내부 login flow를 만들고 `flowID`를 반환한다. Backend는 이를 Redis에 저장한다.
|
||||
|
||||
```text
|
||||
prefixLoginCode:+821012345678 -> flowID
|
||||
TTL: 5m
|
||||
```
|
||||
|
||||
#### Step 4. SMS 인터셉트 및 원격 인증 자동 수립
|
||||
|
||||
Kratos Courier가 SMS 발송 웹훅을 Backend로 호출한다.
|
||||
|
||||
```text
|
||||
POST /api/v1/auth/kratos-courier-relay
|
||||
```
|
||||
|
||||
Backend는 수신 번호에 `prefixLoginCodePhonePending` 플래그가 있는지 확인한다. 플래그가 있으면 외부 SMS 발송을 수행하지 않고, Courier payload 안의 `login_code`를 추출한다.
|
||||
|
||||
이후 Backend는 Redis에서 `flowID`를 조회하고 Kratos에 code 검증을 제출한다.
|
||||
|
||||
```text
|
||||
VerifyLoginCode("+821012345678", flowID, login_code)
|
||||
```
|
||||
|
||||
검증이 성공하면 Kratos가 공식 세션을 발급한다. Backend는 Kratos Courier에 200 OK를 반환하여 Courier 큐를 정상 종료시킨다.
|
||||
|
||||
#### Step 5. OIDC 리다이렉트 합의 및 RP 응답
|
||||
|
||||
Backend는 확보한 subject/session 정보를 바탕으로 Hydra login request를 승인한다.
|
||||
|
||||
```text
|
||||
Hydra.AcceptLoginRequest(login_challenge, subject)
|
||||
```
|
||||
|
||||
성공하면 RP에 다음 정보를 반환한다.
|
||||
|
||||
- `redirectTo`
|
||||
- `sessionId`
|
||||
- `status`
|
||||
|
||||
RP 또는 브라우저는 `redirectTo`를 따라가며 OIDC 흐름을 완료한다.
|
||||
|
||||
### 10.2 표준 여부 판단
|
||||
|
||||
이 흐름은 Kratos의 code login과 Hydra OIDC를 사용한다는 점에서는 표준 컴포넌트를 재사용한다. 그러나 사용자 관점에서 핵심 인증 행위인 SMS 수신, 코드 입력, 명시적 승인 단계가 Backend 내부 자동 처리로 대체된다.
|
||||
|
||||
따라서 이 방식은 "Ory 컴포넌트 기반"이라고 표현할 수는 있지만, "Ory 보안 표준 100% 준수" 또는 "취약점이 전혀 없음"이라고 표현하면 안 된다. Baron이 별도 신뢰 경계와 위험을 만든 비표준 고위험 로그인 방식으로 분류하는 것이 적절하다.
|
||||
|
||||
### 10.3 주요 위험
|
||||
|
||||
이 설계의 위험은 다음과 같다.
|
||||
|
||||
- 전화번호를 아는 것만으로 로그인 요청이 시작될 수 있다.
|
||||
- RP의 `client_assertion`이 유출되거나 오용되면 대량 로그인 시도 통로가 될 수 있다.
|
||||
- SMS가 실제 사용자에게 전달되지 않기 때문에 사용자가 로그인 시도를 인지하지 못할 수 있다.
|
||||
- Courier payload의 `login_code`를 Backend가 내부 자동 검증하는 구조는 감사와 권한 경계가 매우 중요하다.
|
||||
- 사용자의 명시적 승인 없이 Hydra `AcceptLoginRequest`까지 자동 처리되면 "누가 로그인 요청을 승인했는가"가 모호해질 수 있다.
|
||||
|
||||
### 10.4 필수 통제 조건
|
||||
|
||||
Headless 폰번호 로그인을 유지하거나 구현하려면 최소한 다음 조건이 필요하다.
|
||||
|
||||
- 허용 RP allowlist
|
||||
- RP별 `headless_phone_login_enabled` 명시 설정
|
||||
- `private_key_jwt` 기반 `client_assertion` 검증
|
||||
- JWKS 캐시 및 키 회전 정책
|
||||
- 전화번호 enumeration 방지 응답 정책
|
||||
- IP, client, phoneNumber 기준 rate limit
|
||||
- 고위험 계정 또는 관리자 계정 사용 금지
|
||||
- 로그인 성공 즉시 사용자 알림
|
||||
- 사용자의 "내가 아닌 로그인" 선택 시 refresh grant revoke
|
||||
- 모든 단계의 감사 로그 기록
|
||||
|
||||
### 10.5 용어 정리
|
||||
|
||||
설계 문서나 보고서에는 "갈취"라는 표현 대신 다음 용어를 사용하는 것이 적절하다.
|
||||
|
||||
| 피해야 할 표현 | 권장 표현 |
|
||||
| --- | --- |
|
||||
| 코드 갈취 | Courier payload에서 login_code 추출 |
|
||||
| SMS 탈취 | Courier 발송 요청 인터셉트 |
|
||||
| 강제 세션 생성 | Kratos code 검증을 통한 세션 발급 |
|
||||
| 완전 차단 | 정책 기반 차단 또는 위험 완화 |
|
||||
| 취약점이 전혀 없음 | 표준 컴포넌트를 재사용하나 별도 위험 통제 필요 |
|
||||
|
||||
## 11. 팀장 보고용 핵심 결론
|
||||
|
||||
팀장 보고 시에는 다음처럼 정리하는 것이 안전하다.
|
||||
|
||||
1. Baron SSO의 기존 로그인 방식은 표준 2개, 비표준 3개로 분류된다.
|
||||
2. 최근 이슈의 Headless 폰번호 로그인은 기존 비표준 방식보다 더 강한 비표준 확장이다.
|
||||
3. Kratos/Hydra를 사용한다고 해서 전체 흐름이 자동으로 표준 인증이 되는 것은 아니다.
|
||||
4. 사용자가 직접 인증 코드 입력 또는 승인 행동을 하지 않는 흐름은 반드시 별도 보안 통제와 감사 로그가 필요하다.
|
||||
5. "내가 아닌 로그인" 발견 시에는 Naver Works 알림, 사용자 차단 선택, refresh grant revoke, 감사 로그 기록까지 하나의 폐쇄 루프로 설계해야 한다.
|
||||
Reference in New Issue
Block a user