2 Commits
thoon ... main

Author SHA1 Message Date
이태훈
1c54c1c477 Merge branch 'thoon' into main
All checks were successful
ITAM Code Check / build-and-config-check (push) Successful in 13s
ITAM Docker Build Check / docker-build-check (push) Successful in 18s
2026-06-26 17:33:54 +09:00
SDI
933afb02b1 도커 컴포즈 파일 수정(bad gateway issue)
All checks were successful
ITAM Code Check / build-and-config-check (push) Successful in 13s
ITAM Docker Build Check / docker-build-check (push) Successful in 11s
2026-06-26 09:37:58 +09:00
9 changed files with 546 additions and 1 deletions

View File

@@ -0,0 +1,132 @@
# BARON-SSO 통합 로그인 연동 가이드 (자산관리 시스템)
이 문서는 Baron SSO(OpenID Connect IdP)를 자산관리 시스템에 통합 연동하기 위한 상세 가이드입니다. Headless 로그인 방식과 OIDC 표준을 기반으로 사용자 인증 및 정보 획득 과정을 설명하며, Baron SSO `devfront` 페이지에서의 애플리케이션 설정 방법과 연동 시 필요한 핵심 로직을 다룹니다.
---
## 1. 개요
자산관리 시스템에 Baron SSO를 연동하여 통합 로그인을 구현합니다. 이를 통해 사용자는 Baron SSO에서 인증하고, 자산관리 시스템은 사용자의 신원을 확인하고 필요한 정보를 획득하게 됩니다. 본 가이드는 주로 **Headless 로그인** 방식을 사용하며, 이는 사용자에게 Baron SSO의 로그인 화면을 직접 노출하지 않고 자산관리 시스템 내에서 인증 과정을 처리하는 방식입니다.
---
## 2. OIDC 핵심 개념
### 2.1. Headless Login
사용자가 IdP(Baron SSO)의 로그인 화면을 거치지 않고, RP(Relying Party, 자산관리 시스템) 내에서 직접 사용자 인증 정보를 입력받아 백채널(Back-channel)로 인증을 수행하는 방식입니다.
### 2.2. Client Assertion
보안 강화를 위해 RP가 자신이 신뢰할 수 있는 앱임을 증명하는 JWT(JSON Web Token)입니다. 이는 중요한 인증 요청 시 함께 전송됩니다.
### 2.3. 스코프 (Scopes)
사용자 정보에 접근하기 위한 권한 요청 단위입니다.
- `openid`: OIDC 인증 요청임을 명시하며 `id_token` 발급에 필수적입니다.
- `profile`: 사용자의 기본 프로필 정보(이름, 사번, 부서명 등) 접근 권한을 요청합니다.
- `email`: 사용자의 이메일 주소 정보 접근 권한을 요청합니다.
### 2.4. 자동 승인 (Auto-Consent)
Headless 환경에서 사용자의 정보 제공 동의 화면 없이, RP 서버가 백그라운드에서 동의 과정을 자동으로 처리하는 로직입니다.
---
## 3. Baron SSO (devfront) 애플리케이션 생성 및 설정
자산관리 시스템을 Baron SSO에 연동하기 위해 `devfront` 관리자 도구에서 Headless RP를 설정해야 합니다. 이때 IdP 서버가 RP의 JWKS 엔드포인트에 접속할 수 있어야 함을 유의하십시오.
### 3.1. Step 1: 클라이언트 기본 정보 입력
1. `devfront`에 접속하여 **연동 앱 > 연동 앱 추가**를 클릭합니다.
2. **Name**: `asset-management-system` (또는 자산관리 시스템의 이름을 입력)
3. **Redirect URIs**: 자산관리 시스템의 콜백 경로를 입력합니다.
- 예: `http://[자산관리_시스템_IP_또는_도메인]:[포트]/callback`
- **주의**: 실제 자산관리 시스템에서 요청하는 Redirect URI와 여기서 등록한 값이 **완전히 일치**해야 인증 거부 에러가 발생하지 않습니다.
4. **Scopes**: `openid`, `profile`, `email`을 선택합니다.
5. **Type**: 반드시 `pkce`를 선택합니다.
### 3.2. Step 2: Headless 기능 및 보안 설정
1. `pkce` 하단의 **"Headless Login (자체 로그인 UI 사용)"** 토글을 활성화합니다.
2. **JWKS URI**: **Baron SSO 서버에서 접근 가능한** 자산관리 시스템의 공개키 주소를 입력합니다.
- 예: `http://[자산관리_시스템_IP_또는_도메인]:[포트]/.well-known/jwks.json`
- **주의**: Baron SSO 서버에서 자산관리 시스템의 포트로 네트워크가 열려있어야 합니다.
### 3.3. Step 3: 저장 및 확인 (JWKS 캐시 검증)
1. **앱 생성** 버튼을 클릭하여 설정을 저장합니다.
2. 연동 앱 목록에서 생성된 앱이 **PKCE (Headless Login)** 유형인지 확인합니다.
3. 앱 상세 페이지 하단이나 설정 탭에서 **JWKS 캐시 상태**가 `Success`인지 반드시 확인합니다. 캐시 상태가 비어있거나 실패인 경우, 자산관리 시스템이 켜져 있는지 확인하고 **새로고침(Refresh)** 버튼을 눌러 수동으로 캐시를 갱신해야 합니다.
---
## 4. 자산관리 시스템 연동 로직
### 4.1. 사번 로그인 (Headless Employee ID Login Flow)
사용자가 자산관리 시스템에서 사번과 비밀번호를 입력하여 로그인하는 시퀀스입니다.
1. **신호 요청**: 자산관리 시스템은 Baron SSO의 Authorization Endpoint(`oauth2/auth`)에 요청하여 `login_challenge`를 획득합니다.
2. **본인 인증**: 자산관리 시스템은 Private Key로 서명된 `client_assertion`(JWT)과 사용자의 사번, 비밀번호, `login_challenge`를 포함하여 Baron SSO의 Headless 로그인 API(`POST /api/v1/auth/headless/password/login`)를 호출합니다.
3. **권한 획득 (자동 승인 포함)**: 인증 성공 후 Baron SSO가 반환하는 `redirectTo` 주소를 추적하며, 이 과정에서 `consent` 화면이 감지되면 자산관리 시스템은 백그라운드에서 동의 API(`POST /api/v1/auth/consent/accept`)를 호출하여 자동으로 승인합니다. 최종적으로 `Authorization Code`를 획득합니다.
4. **인증키 발급**: 획득한 `Authorization Code`와 새로 생성한 `client_assertion`을 사용하여 Baron SSO의 Token Endpoint에 요청, `id_token``access_token`을 발급받습니다.
5. **로그인 완료**: `id_token`을 검증하고 사용자 정보를 추출하여 자산관리 시스템의 세션을 생성하고 로그인 완료 처리합니다.
### 4.2. 전화번호 인증 로그인 (Headless Phone Number Authentication Login Flow)
사용자가 자산관리 시스템에서 전화번호를 입력하여 인증 링크를 받고, 모바일에서 승인하여 로그인하는 시퀀스입니다.
1. **신호 요청**: 사번 로그인과 동일하게 `login_challenge`를 획득합니다.
2. **링크 발송 요청**: 자산관리 시스템은 `client_assertion`, `login_challenge`, 사용자의 전화번호(`loginId`)를 포함하여 Baron SSO의 인증 링크 발송 API(`POST /api/v1/auth/headless/link/init`)를 호출합니다. Baron SSO는 사용자에게 인증 링크를 발송하고 `pendingRef`를 반환합니다.
3. **인증 상태 폴링**: 자산관리 시스템은 `pendingRef``client_assertion`을 포함하여 Baron SSO의 상태 확인 API(`POST /api/v1/auth/headless/link/poll`)를 주기적으로 호출합니다. 사용자가 모바일에서 링크를 클릭하여 인증을 완료하면, Baron SSO는 `redirectTo` 정보를 반환하며 폴링이 종료됩니다.
4. **이후 과정**: 폴링 결과로 받은 `redirectTo` 주소부터는 사번 로그인과 동일하게 표준 OIDC 흐름을 따릅니다 (자동 승인, `Authorization Code` 획득, `id_token` 발급, 세션 생성).
### 4.3. 자동 승인 및 스코프 처리 (Auto-Consent and Scope Handling)
자산관리 시스템은 `openid`, `profile`, `email`과 같은 스코프를 Baron SSO에 요청합니다. Headless 환경에서는 SSO 서버로부터 리다이렉트 주소에 `/consent`가 포함될 경우, 자산관리 시스템은 이를 감지하여 `consent_challenge`를 추출하고, 동의 상세 정보(`GET /api/v1/auth/consent`)를 요청하여 필요한 스코프를 확인합니다. 이후, 확인된 스코프를 모두 허용(`grant_scope`)한다고 Baron SSO의 동의 승인 API(`POST /api/v1/auth/consent/accept`)에 전송하여 자동으로 승인 과정을 처리합니다.
### 4.4. Tenant 접근 제한 예외 처리 (Tenant Access Restriction Exception Handling)
자산관리 시스템이 `tenant_access_restricted=true`로 설정되어 있고, 현재 사용자의 tenant가 허용된 `allowed_tenants`에 포함되지 않는 경우, Baron SSO는 `403` HTTP 상태 코드와 `code=tenant_not_allowed`를 응답합니다. 자산관리 시스템은 이 응답을 감지하여 사용자에게 적절한 에러 페이지(예: `/ko/error?error=tenant_not_allowed...`)로 리다이렉트하여 처리해야 합니다.
---
## 5. 자산관리 시스템 로컬 설정 (.env)
자산관리 시스템 프로젝트 루트의 `.env` 파일에 다음과 같이 Baron SSO 연동 설정을 구성합니다.
```env
# 애플리케이션 실행 포트 (자산관리 시스템의 포트)
PORT=3000 # 예시
# OIDC IdP 설정 (Baron SSO 서버 주소)
CLIENT_ID=... # devfront에서 발급받은 클라이언트 ID
ISSUER=http://[BARON_SSO_IP_또는_도메인]/oidc
# 리다이렉트 및 보안 설정
# REDIRECT_URI는 DevFront에 등록한 Redirect URIs 중 하나와 정확히 일치해야 합니다.
REDIRECT_URI=http://[자산관리_시스템_IP_또는_도메인]:[포트]/callback
# JWKS_URI는 Baron SSO 서버가 자산관리 시스템으로 접속할 때 사용하는 주소와 일치해야 합니다.
JWKS_URI=http://[자산관리_시스템_IP_또는_도메인]:[포트]/.well-known/jwks.json
# tenant 제한 시 이동시킬 userfront 에러 경로
ERROR_LOCALE_PATH=/ko/error
```
---
## 6. 주의 사항 (네트워크 구성)
- **서버 간 통신 (Server-to-Server)**: Headless 로그인은 Baron SSO 서버가 직접 자산관리 시스템의 `JWKS_URI`에 접속하여 서명을 검증합니다. 따라서 IdP 서버에서 자산관리 시스템의 포트로의 인바운드 통신이 허용되어야 합니다. 방화벽 및 네트워크 설정을 확인하십시오.
- **Redirect URI 일치**: 브라우저를 통해 접속할 주소가 IP라면, Redirect URI와 JWKS URI 모두 IP 기반 주소로 통일하는 것이 문제 발생 소지를 줄이는 가장 좋은 방법입니다. 도메인을 사용한다면 일관되게 도메인으로 설정해야 합니다.
---
## 7. 실행
자산관리 시스템 프로젝트에서 다음 명령어를 실행하여 필요한 패키지를 설치하고 서버를 시작합니다.
```bash
npm install
npm start
```
서버 실행 시 `keys.json` 파일이 자동으로 생성되며, `.env`에 설정된 `JWKS_URI` 경로로 공개키가 서빙되어 Baron SSO가 이를 검증에 사용할 수 있게 됩니다.

View File

@@ -0,0 +1,119 @@
# Headless 스코프(Scope) 및 자동 승인(Auto-Consent) 가이드
이 문서는 Headless 로그인 환경에서 애플리케이션이 사용자 정보를 가져오기 위해 사용하는 **스코프(Scope)**의 개념과, IdP의 화면 없이 권한을 획득하는 **자동 승인(Auto-Consent)** 로직을 설명합니다.
---
## 1. 요구 스코프 (Requested Scopes)
데모 앱은 OIDC 표준에 따라 다음 스코프를 IdP(Baron SSO)에 요청합니다.
| 스코프 | 필수 여부 | 역할 및 획득 데이터 |
| :-------- | :-------: | :------------------------------------------------------------------ |
| `openid` | **필수** | 해당 요청이 OIDC 인증임을 명시. `id_token` 발급을 위해 반드시 필요. |
| `profile` | 권장 | 사용자의 기본 프로필 정보(이름, 사번, 부서명 등) 접근 권한. |
| `email` | 권장 | 사용자의 이메일 주소 정보 접근 권한. |
데모 앱은 최초 인증 요청(`GET /oauth2/auth`) 시 `scope=openid profile` 형식으로 권한을 요구합니다.
---
## 2. 자동 승인(Auto-Consent) 흐름도
일반적인 OIDC 환경에서는 사용자에게 '정보 제공 동의' 화면이 노출되지만, Headless 환경에서는 **RP 서버가 이 과정을 백그라운드에서 자동으로 수행**합니다.
```mermaid
sequenceDiagram
autonumber
participant Browser as 사용자 브라우저
participant RP as 데모 앱 서버 (RP)
participant SSO as Baron SSO (IdP)
RP->>SSO: 1. 본인 인증 요청 (전화번호)
SSO-->>RP: 2. 인증 성공 및 리다이렉트 (redirectTo: /consent...)
Note over RP: [자동 승인 로직 시작]
RP->>RP: 3. 리다이렉트 경로 중 '/consent' 감지
RP->>SSO: 4. 동의 상세 정보 요청 (GET /api/v1/auth/consent)
SSO-->>RP: 5. 요청된 스코프 정보 반환 (openid, profile 등)
RP->>SSO: 6. 동의 승인 API 호출 (POST /api/v1/auth/consent/accept)<br/>[grant_scope 포함]
SSO-->>RP: 7. 최종 리다이렉트 주소 반환 (code=...)
Note over RP: [표준 OIDC 흐름 복귀]
RP->>SSO: 8. Token Exchange (Code -> id_token)
SSO-->>RP: 9. 사용자 정보가 담긴 id_token 발급
RP-->>Browser: 10. 세션 생성 및 로그인 완료 안내
```
---
## 3. 핵심 구현 코드 (server.js)
데모 앱의 `server.js``resolveRedirects` 함수는 SSO 서버로부터 오는 리다이렉트 주소를 추적하다가, 동의 화면(`consent_challenge`)이 나타나면 이를 가로채서 처리합니다.
```javascript
if (currentUrl.includes("/consent")) {
console.log(
" [자동 승인] 사용자의 정보 제공 동의 화면이 감지되어 시스템이 자동으로 승인 중입니다.",
);
// 1. URL에서 consent_challenge 추출
const consentUrl = new URL(currentUrl);
const consentChallenge = consentUrl.searchParams.get("consent_challenge");
// 2. SSO 서버에 현재 요청된 스코프가 무엇인지 확인
const detailsUrl = new URL("/api/v1/auth/consent", currentUrl);
detailsUrl.searchParams.set("consent_challenge", consentChallenge);
const detailsRes = await fetch(detailsUrl.toString(), {
headers: { Cookie: nextCookies },
});
const consentInfo = await detailsRes.json();
// 3. 확인된 스코프(openid, profile 등)를 모두 허용(accept)한다고 서버에 전송
const acceptUrl = new URL("/api/v1/auth/consent/accept", currentUrl);
const acceptRes = await fetch(acceptUrl.toString(), {
method: "POST",
headers: { "Content-Type": "application/json", Cookie: nextCookies },
body: JSON.stringify({
consent_challenge: consentChallenge,
grant_scope: consentInfo.requested_scope || ["openid", "profile"],
grant_access_token_audience:
consentInfo.requested_access_token_audience || [],
}),
});
const acceptPayload = await acceptRes.json();
// 4. 승인 후 받은 최종 목적지(code 포함)로 이동 계속
return resolveRedirects(acceptPayload.redirectTo, nextCookies, depth + 1);
}
```
## 4. Tenant 접근 제한 예외 흐름
Headless RP도 consent 단계에서 tenant 제한에 걸릴 수 있습니다. 이 경우는 일반적인 auto-consent 성공 흐름과 별도로 처리해야 합니다.
조건:
- RP metadata에 `tenant_access_restricted=true`
- 현재 사용자의 tenant가 `allowed_tenants`에 없음
이때 Baron SSO 응답:
- `GET /api/v1/auth/consent` 또는 `POST /api/v1/auth/consent/accept`
- HTTP `403`
- body에 `code=tenant_not_allowed`
데모 앱 처리 원칙:
- `redirectTo`가 없다고 가정하고 계속 진행하면 안 됩니다.
- `tenant_not_allowed`를 감지하면 `userfront` 에러 화면 URL로 변환해 브라우저를 이동시켜야 합니다.
- 기본 목적지는 `/ko/error?error=tenant_not_allowed&error_description=...&details=...` 입니다.
운영 관점 해석:
- backend 로그에 `tenant_not_allowed`가 보이면 Baron SSO 제한은 정상입니다.
- 브라우저에서 `Invalid URL`이 보이면 RP가 `403` 응답을 잘못 소비하고 있다는 뜻입니다.
---
## 4. 특징 및 장점
- **심리스한 UX**: 사용자는 "로그인 중..."이라는 메시지만 보게 되며, 복잡한 권한 동의 절차를 신경 쓸 필요가 없습니다.
- **강력한 보안**: 자동 승인 과정은 서버 대 서버(Back-channel) 통신으로 이루어지며, 브라우저에는 동의 관련 데이터가 전혀 노출되지 않습니다.
- **유연한 확장**: 나중에 새로운 사용자 정보(`email` 등)가 필요해지더라도, 앱의 스코프 설정만 변경하면 자동으로 승인 로직에 반영됩니다.

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초)으로 제한되며, 이후에는 요청을 처음부터 다시 시작해야 합니다.

View File

@@ -0,0 +1,115 @@
# 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
# tenant 제한 시 이동시킬 userfront 에러 경로
ERROR_LOCALE_PATH=/ko/error
```
---
## 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가 검증에 사용할 수 있게 됩니다.
---
## 5. Tenant 제한 테스트 가이드
Headless RP는 login API 성공 이후 consent 단계에서 tenant 제한이 걸릴 수 있습니다. 따라서 단순히 `/api/v1/auth/headless/password/login` 성공만 보면 안 되고, 최종 redirect 해석까지 확인해야 합니다.
### 권장 검증 순서
1. 제한이 없는 상태에서 로그인 성공 여부를 먼저 확인합니다.
2. `devfront`에서 대상 RP에 `tenant_access_restricted``allowed_tenants`를 설정합니다.
3. 허용된 tenant 사용자로 로그인해 정상 성공 여부를 확인합니다.
4. 허용되지 않은 tenant 사용자로 로그인해 `userfront``/ko/error?error=tenant_not_allowed...` 화면으로 이동하는지 확인합니다.
### 기대 동작
- `tenant_not_allowed`는 Baron SSO backend가 `403`으로 반환합니다.
- headless demo는 이 응답을 감지해 `userfront` 에러 화면으로 브라우저를 이동시킵니다.
- 따라서 브라우저 팝업으로 `Invalid URL`이 보인다면 demo 쪽 에러 처리 누락을 먼저 의심해야 합니다.
### 로그 확인 포인트
```bash
docker logs --tail 100 headless-login-demo
docker logs --tail 100 baron_backend
```
- `headless-login-demo`
- consent 단계에서 `tenant_not_allowed`를 받아 `errorUrl`로 변환했는지 확인
- `baron_backend`
- `GET /api/v1/auth/consent` 또는 `POST /api/v1/auth/consent/accept``403`인지 확인
- 해당 응답 body에 `code=tenant_not_allowed`가 포함되는지 확인

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

View File

@@ -40,7 +40,7 @@ services:
ports:
- "9090:80"
volumes:
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
- ./logs/nginx:/var/log/nginx
depends_on:
backend: