headless 가이드 문서 추가

This commit is contained in:
2026-04-13 15:20:54 +09:00
parent 1a7fc805c0
commit 50e3b2040e
6 changed files with 259 additions and 0 deletions

View File

@@ -0,0 +1,95 @@
# Headless 사번 로그인 로직
이 문서는 사용자가 데모 애플리케이션에서 **사번과 비밀번호**를 이용해 로그인할 때 발생하는 내부 OIDC (OpenID Connect) Headless 인증 흐름을 설명합니다.
## 핵심 개념
- **Headless Login**: 사용자가 IdP(Identity Provider, Baron SSO)의 로그인 화면을 거치지 않고, RP(Relying Party, 데모 앱) 내에서 직접 아이디와 비밀번호를 입력받아 백채널(Back-channel)로 인증을 수행하는 방식입니다.
- **Client Assertion**: 보안을 위해 데모 앱은 사번/비밀번호를 전송할 때 자신이 신뢰할 수 있는 앱임을 증명하는 JWT(`client_assertion`)를 생성하여 함께 전송합니다.
---
## 1. 사번 로그인 시퀀스 다이어그램
```mermaid
sequenceDiagram
autonumber
actor User as 사용자
participant RP as 데모 앱 (RP)
participant OIDC as Baron SSO (OIDC/Hydra)
participant Auth as Baron SSO (Headless API)
User->>RP: 1. 사번 / 비밀번호 입력 후 로그인 요청
note over RP: [1단계: 신호 요청]
RP->>OIDC: 2. OIDC Discovery 및 Authorization Request<br/>(GET /oauth2/auth?response_type=code...)
OIDC-->>RP: 3. login_challenge 발급 및 302 Redirect
note over RP: [2단계: 본인 인증]
RP->>RP: 4. Private Key로 client_assertion (JWT) 생성
RP->>Auth: 5. Headless 로그인 API 호출<br/>(POST /api/v1/auth/headless/password/login)<br/>[client_assertion, login_challenge, 사번, 비밀번호 포함]
Auth-->>RP: 6. 인증 성공 및 리다이렉트 URL 반환
note over RP: [3단계: 권한 획득]
RP->>OIDC: 7. 리다이렉트 URL 추적 (Cookie 유지)
OIDC-->>RP: 8. Consent 화면 (필요 시 자동 승인 API 호출)
OIDC-->>RP: 9. 최종 Authorization Code 발급 (code=...)
note over RP: [4단계: 인증키 발급]
RP->>OIDC: 10. Token Endpoint 호출 (Authorization Code Grant)<br/>[client_assertion 포함]
OIDC-->>RP: 11. id_token, access_token 발급
note over RP: [5단계: 로그인 완료]
RP->>RP: 12. id_token 검증 및 사용자 세션 생성
RP-->>User: 13. 홈 화면 리다이렉트 및 로그인 성공
```
---
## 2. 단계별 상세 로직 설명
위 다이어그램의 주요 단계를 데모 애플리케이션(`server.js``runHeadlessSsoLogin` 함수) 로직을 중심으로 설명합니다.
### [1단계] 신호 요청 (Authorization Request)
- **목적**: SSO 서버와의 세션을 시작하고, 인증 컨텍스트를 식별할 수 있는 고유값(`login_challenge`)을 획득합니다.
- **동작**:
1. OIDC Discovery를 통해 엔드포인트들을 가져옵니다.
2. `response_type=code`, `client_id`, `state`, `nonce` 등을 포함하여 Authorization Endpoint(`/oauth2/auth`)로 `fetch` 요청(리다이렉트 수동 추적 모드)을 보냅니다.
3. 응답 헤더의 `Location`에 포함된 `login_challenge` 값을 추출합니다.
### [2단계] 본인 인증 (Headless Password Login)
- **목적**: 사용자가 입력한 자격 증명(사번, 비밀번호)을 SSO 서버에 직접 전달하여 인증을 승인받습니다.
- **동작**:
1. RP의 **Private Key(`keys.json`)**를 사용하여 `client_assertion` JWT를 서명합니다. (이때 `aud` 값은 SSO 서버의 Headless API URL이어야 합니다.)
2. `POST /api/v1/auth/headless/password/login` 엔드포인트로 JSON 페이로드를 전송합니다.
```json
{
"client_id": "...",
"login_challenge": "...",
"loginId": "사번",
"password": "비밀번호",
"client_assertion": "..."
}
```
3. 인증이 성공하면 서버는 OIDC 로그인 흐름을 계속할 수 있는 `redirectTo` 주소를 반환합니다.
### [3단계] 권한 획득 (Redirect Resolution & Consent)
- **목적**: 인증 성공 후, OIDC 프로토콜에 따라 최종적으로 `Authorization Code`를 획득합니다.
- **동작**:
1. 2단계에서 받은 `redirectTo` 주소를 따라가며 쿠키(Cookie)를 계속 유지시킵니다.
2. 만약 경로 중에 정보 제공 동의(`consent`) 화면이 감지되면, 데모 앱이 사용자 대신 백그라운드에서 동의 API(`POST /api/v1/auth/consent/accept`)를 호출하여 자동 승인합니다.
3. 최종적으로 URL에 `code=...`가 포함된 리다이렉트를 찾습니다.
### [4단계] 인증키 발급 (Token Exchange)
- **목적**: 획득한 `Authorization Code`를 안전한 토큰들로 교환합니다.
- **동작**:
1. Token Endpoint로 코드를 전송합니다.
2. 이때도 보안을 위해 `private_key_jwt` 방식이 사용되므로, 새로 생성한 `client_assertion`을 요청 본문에 포함시킵니다.
3. 검증이 완료되면 `access_token`과 사용자 정보가 담긴 `id_token`을 받게 됩니다.
### [5단계] 로그인 완료 (Session Creation)
- **목적**: 받은 토큰을 검증하고, 브라우저가 인식할 수 있는 로컬 세션을 만듭니다.
- **동작**:
1. `jose` 라이브러리를 통해 `id_token`의 서명을 검증하고 payload(사번, 이름, 부서 등)를 추출합니다.
2. 추출된 사용자 정보를 `express-session`의 `req.session.user`에 저장합니다.
3. 클라이언트 브라우저에게 세션 쿠키를 발급하며 홈 화면(`/home.html`)으로 리다이렉트 시킵니다.

View File

@@ -0,0 +1,84 @@
# Headless 전화번호 인증 로그인 로직
이 문서는 사용자가 데모 애플리케이션에서 **전화번호**를 입력하여 인증 링크를 받고, 모바일에서 승인하여 로그인하는 내부 흐름을 설명합니다.
## 핵심 개념
- **Link Init**: 사용자의 전화번호로 실제 인증 링크(카카오톡 또는 SMS)를 발송하도록 SSO 서버에 요청하는 단계입니다.
- **Polling**: 사용자가 모바일에서 링크를 클릭할 때까지 앱 서버가 주기적으로 SSO 서버에 "인증이 완료되었는지" 물어보는 과정입니다.
- **Security**: 이 과정에서도 모든 API 호출은 RP의 Private Key로 서명된 `client_assertion`을 포함하여 보안을 유지합니다.
---
## 1. 전화번호 로그인 시퀀스 다이어그램
```mermaid
sequenceDiagram
autonumber
actor User as 사용자
participant RP as 데모 앱 (RP)
participant OIDC as Baron SSO (OIDC)
participant Auth as Baron SSO (Headless API)
User->>RP: 1. 전화번호 입력 후 인증 요청
note over RP: [1단계: 신호 요청]
RP->>OIDC: 2. Authorization Request (GET /oauth2/auth)<br/>[login_challenge 획득]
note over RP: [2단계: 링크 발송 요청]
RP->>Auth: 3. 인증 링크 발송 요청 (POST .../link/init)<br/>[client_assertion, login_challenge, 전화번호 포함]
Auth-->>User: 4. 인증 링크 발송 (카카오톡/SMS)
Auth-->>RP: 5. 대기 참조값(pendingRef) 반환
note over RP: [3단계: 인증 상태 폴링]
loop 인증 완료 시까지 반복 (최대 3분)
RP->>Auth: 6. 상태 확인 (POST .../link/poll)<br/>[client_assertion, pendingRef 포함]
alt 아직 대기 중
Auth-->>RP: 7. authorization_pending 응답
RP->>RP: 잠시 대기 (interval)
else 사용자 링크 클릭 완료
Auth-->>RP: 8. 인증 성공 및 리다이렉트 URL 반환
end
end
note over User: 사용자가 모바일에서 링크 클릭 및 승인
note over RP: [4단계: 이후 과정 (사번 로그인과 동일)]
RP->>OIDC: 9. 리다이렉트 추적 및 Authorization Code 획득
RP->>OIDC: 10. Token Endpoint 호출 및 id_token 발급
RP->>RP: 11. 세션 생성 및 홈 화면 이동
RP-->>User: 12. 로그인 완료 안내
```
---
## 2. 단계별 상세 로직 설명
### [1단계] 신호 요청 (Login Challenge)
- 사번 로그인과 동일하게 OIDC 표준 흐름을 시작하기 위해 `login_challenge`를 먼저 획득합니다. 이 값은 이후 인증 시도 시 "이 사용자가 어떤 OIDC 요청에 응답하고 있는지"를 SSO 서버에 알려주는 매개체가 됩니다.
### [2단계] 링크 발송 요청 (Link Init)
- **동작**: `POST /api/v1/auth/headless/link/init` 호출.
- **데이터**: `client_assertion`, `login_challenge`, 사용자의 `loginId`(전화번호).
- **결과**: SSO 서버는 사용자의 전화번호로 인증 가능한 고유 링크를 발송하고, RP에게는 해당 요청을 추적할 수 있는 `pendingRef`와 다음 폴링까지의 권장 대기 시간(`interval`)을 반환합니다.
### [3단계] 인증 상태 폴링 (Polling)
- **동작**: `POST /api/v1/auth/headless/link/poll`을 주기적으로 호출.
- **상태 처리**:
- `authorization_pending`: 사용자가 아직 링크를 클릭하지 않았으므로 지정된 시간만큼 기다린 후 다시 시도합니다.
- `slow_down`: 폴링 간격이 너무 잦다는 신호로, 대기 시간을 조금 더 늘립니다.
- `expired_token`: 3분이 경과하여 인증 요청이 만료된 경우입니다.
- **성공**: 사용자가 승인을 완료하면 서버는 `redirectTo` 정보를 반환하며 폴링이 종료됩니다.
### [4단계] 이후 과정 (Standard OIDC Flow)
- 폴링 결과로 받은 `redirectTo` 주소부터는 표준 OIDC 흐름을 따릅니다.
- 데모 앱은 해당 주소로 리다이렉트하며 발생하는 쿠키를 유지하고, 필요 시 사용자 동의(Consent) 과정을 자동 수행하여 최종적으로 `Authorization Code`를 얻어냅니다.
- 마지막으로 해당 코드를 `id_token`으로 교환하여 사용자 정보를 세션에 저장합니다.
---
## 3. 특징 및 주의 사항
- **비동기 사용자 경험**: 사용자는 PC에서 전화번호만 입력하고, 실제 인증 행위는 모바일에서 수행합니다. PC 화면은 폴링이 완료될 때까지 "인증 대기 중" 상태를 유지합니다.
- **보안 검증**: 폴링 시에도 `client_assertion`이 필요합니다. 이는 제3자가 `pendingRef`를 가로채더라도 RP의 비밀키 없이는 인증 상태를 훔쳐볼 수 없음을 보장합니다.
- **제한 시간**: 보안상 폴링은 보통 3분(180초)으로 제한되며, 이후에는 요청을 처음부터 다시 시작해야 합니다.

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

80
docs/setup-guide.md Normal file
View File

@@ -0,0 +1,80 @@
# Headless 로그인 데모 설정 가이드 (Baron SSO 연동)
이 문서는 `headless-login-demo` 프로젝트를 설치하고, **Baron SSO (OIDC IdP)** 관리자 도구(`devfront`)에서 **Headless RP**를 올바르게 생성 및 설정하는 방법을 단계별로 설명합니다.
---
## 1. Baron SSO (devfront) 설정
Headless RP는 일반 OIDC 클라이언트와 설정 방식이 다릅니다. 특히 **IdP 서버가 RP(데모 앱)의 JWKS 엔드포인트에 접속할 수 있어야 함**을 유의하십시오.
### Step 1: 클라이언트 기본 정보 입력
1. `devfront` 접속 후 **연동 앱 > 연동 앱 추가**를 클릭합니다.
2. **Name**: `headless-login` (또는 자유롭게 입력)
3. **Redirect URIs**: 데모 앱에 접속할 주소의 콜백 경로를 입력합니다.
- **통합 환경 (IdP와 RP가 같은 서버일 때)**: `http://localhost:3000/callback`
- **분리 환경 (IdP는 원격 서버, RP는 내 PC일 때)**: `http://[내_PC_IP]:3000/callback`
- _주의: 실제 앱에서 요청하는 Redirect URI와 여기서 등록한 값이 **완전히 일치**해야 인증 거부 에러가 발생하지 않습니다._
4. **Scopes**: `openid`, `profile`, `email`을 선택합니다.
5. **Type**: 반드시 `pkce`를 선택해야 합니다.
![Step 1 설정 예시](./images/setup-step1-example.png)
### Step 2: Headless 기능 및 보안 설정
1. `pkce` 하단의 **"Headless Login (자체 로그인 UI 사용)"** 토글을 활성화합니다.
2. **JWKS URI**: **Baron SSO 서버에서 접근 가능한** 데모 앱의 공개키 주소를 입력합니다.
- **통합 환경 (IdP와 RP가 같은 서버일 때)**: `http://localhost:3000/.well-known/jwks.json`
- **분리 환경 (IdP는 원격 서버, RP는 내 PC일 때)**: `http://[내_PC_IP]:3000/.well-known/jwks.json`
- _주의: Baron SSO 서버(`172.16.10.175`)에서 사용자님의 로컬 PC 포트로 네트워크가 열려있어야 합니다._
![Step 2 설정 예시](./images/setup-step2-example.png)
### Step 3: 저장 및 확인 (JWKS 캐시 검증)
1. **앱 생성** 버튼을 클릭해 저장합니다.
2. 연동 앱 목록에서 해당 앱이 **PKCE (Headless Login)** 유형으로 생성됐는지 확인합니다.
3. 앱 상세 페이지 하단이나 설정 탭에서 **JWKS 캐시 상태**가 `Success`인지 반드시 확인합니다.
- _팁: 캐시 상태가 비어있거나 실패인 경우, 데모 앱(내 PC)이 켜져 있는지 확인하고 **새로고침(Refresh)** 버튼을 눌러 수동으로 캐시를 갱신하세요. 캐싱이 정상적으로 이루어져야만 로그인이 작동합니다._
## ![Step 3 설정 예시](./images/setup-step3-example.png)
## 2. Headless Login 애플리케이션 로컬 설정 (.env)
`devfront`에서 설정한 값을 바탕으로 프로젝트 루트의 `.env` 파일을 구성합니다.
```env
# 애플리케이션 실행 포트
PORT=3000
# OIDC IdP 설정 (원격 서버 주소 입력)
CLIENT_ID=a9b64539-7242-4aa5-ad3d-13c7f1ef00f2
ISSUER=http://172.16.10.175/oidc
# 리다이렉트 및 보안 설정
# REDIRECT_URI는 DevFront에 등록한 Redirect URIs 중 하나와 정확히 일치해야 합니다.
REDIRECT_URI=http://[내_PC_IP]:3000/callback
# JWKS_URI는 Baron SSO 서버가 내 PC로 접속할 때 사용하는 주소와 일치해야 합니다.
JWKS_URI=http://[내_PC_IP]:3000/.well-known/jwks.json
```
---
## 3. 주의 사항 (네트워크 구성)
- **서버 간 통신 (Server-to-Server)**: Headless 로그인은 Baron SSO 서버가 직접 데모 앱의 `JWKS_URI`에 접속하여 서명을 검증합니다. 따라서 IdP 서버에서 내 로컬 PC의 포트(`3000`)로의 인바운드 통신이 허용되어야 합니다.
- **Redirect URI 일치**: 브라우저를 통해 접속할 주소가 IP라면, Redirect URI와 JWKS URI 모두 IP 기반 주소로 통일하는 것이 문제 발생 소지를 줄이는 가장 좋은 방법입니다.
---
## 4. 실행
```bash
npm install
npm start
```
> **💡 참고: 자동 키 생성 및 JWKS 엔드포인트 활성화**
> `npm start` 명령어로 서버를 실행하면, 프로젝트 내에 `keys.json` 파일이 자동으로 생성됩니다. 이 파일에는 서버가 자체적으로 발급한 RSA 보안 키 쌍(공개키/개인키)이 저장됩니다.
> 동시에, `.env`에 설정된 `JWKS_URI` 경로(또는 기본 경로)로 공개키가 서빙되어 Baron SSO가 검증에 사용할 수 있게 됩니다.