Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c54c1c477 | ||
| 933afb02b1 |
132
baron-sso_login_guide/BARONSSO_loginAPI_task.md
Normal file
132
baron-sso_login_guide/BARONSSO_loginAPI_task.md
Normal 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가 이를 검증에 사용할 수 있게 됩니다.
|
||||
119
baron-sso_login_guide/headless-consent-and-scope.md
Normal file
119
baron-sso_login_guide/headless-consent-and-scope.md
Normal 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` 등)가 필요해지더라도, 앱의 스코프 설정만 변경하면 자동으로 승인 로직에 반영됩니다.
|
||||
95
baron-sso_login_guide/headless-employee-login-flow.md
Normal file
95
baron-sso_login_guide/headless-employee-login-flow.md
Normal 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`)으로 리다이렉트 시킵니다.
|
||||
84
baron-sso_login_guide/headless-phone-login-flow.md
Normal file
84
baron-sso_login_guide/headless-phone-login-flow.md
Normal 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초)으로 제한되며, 이후에는 요청을 처음부터 다시 시작해야 합니다.
|
||||
115
baron-sso_login_guide/setup-guide.md
Normal file
115
baron-sso_login_guide/setup-guide.md
Normal 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 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 3: 저장 및 확인 (JWKS 캐시 검증)
|
||||
|
||||
1. **앱 생성** 버튼을 클릭해 저장합니다.
|
||||
2. 연동 앱 목록에서 해당 앱이 **PKCE (Headless Login)** 유형으로 생성됐는지 확인합니다.
|
||||
3. 앱 상세 페이지 하단이나 설정 탭에서 **JWKS 캐시 상태**가 `Success`인지 반드시 확인합니다.
|
||||
- _팁: 캐시 상태가 비어있거나 실패인 경우, 데모 앱(내 PC)이 켜져 있는지 확인하고 **새로고침(Refresh)** 버튼을 눌러 수동으로 캐시를 갱신하세요. 캐싱이 정상적으로 이루어져야만 로그인이 작동합니다._
|
||||
|
||||
## 
|
||||
|
||||
## 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`가 포함되는지 확인
|
||||
BIN
baron-sso_login_guide/setup-step1-example.png
Normal file
BIN
baron-sso_login_guide/setup-step1-example.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 191 KiB |
BIN
baron-sso_login_guide/setup-step2-example.png
Normal file
BIN
baron-sso_login_guide/setup-step2-example.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 186 KiB |
BIN
baron-sso_login_guide/setup-step3-example.png
Normal file
BIN
baron-sso_login_guide/setup-step3-example.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 95 KiB |
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user