1
0
forked from baron/baron-sso

백채널 로그아웃 문서 추가

This commit is contained in:
2026-06-19 10:05:10 +09:00
parent 7145e703d7
commit 0ab2c01718

View File

@@ -0,0 +1,237 @@
# Back-Channel Logout 통합 가이드
이 문서는 Baron SSO와 연동하는 RP들이 `Back-Channel Logout`을 어떻게 처리하는지 한 곳에서 정리합니다.
핵심은 다음 두 가지입니다.
1. **Baron SSO가 RP로 보내는 요청 형식은 공통입니다.**
2. **RP가 그 요청을 받아 세션을 찾고 지우는 내부 로직은 RP 유형에 따라 달라질 수 있습니다.**
## 결론
Baron SSO는 모든 RP에 대해 같은 방식으로 `POST /backchannel-logout`를 보냅니다.
- `Content-Type: application/x-www-form-urlencoded`
- body: `logout_token=<jwt>`
- 검증용 공개키는 `GET /api/v1/auth/backchannel/jwks.json`
차이는 Baron이 보낸 뒤 **RP 내부에서 세션을 어디서 찾고 어떻게 파기하느냐**입니다.
- `PKCE RP`
- `server-side-app RP`
- `headless login RP``PKCE` 기반 custom login UI 변형
## Polling 방식에 대한 정리
`polling`**OIDC Back-Channel Logout 표준 자체에는 없습니다.**
공식 사양은 OP가 RP의 `back-channel logout URI`로 직접 `HTTP POST`를 보내고, 본문에 `logout_token`을 포함하는 방식만 정의합니다. OIDC Back-Channel Logout은 OP와 RP 사이의 direct back-channel communication이며, RP가 주기적으로 OP를 조회해서 로그아웃 여부를 받는 polling 모델은 표준 경로가 아닙니다.
공식 사양 근거: [`OpenID Connect Back-Channel Logout 1.0`](https://openid.net/specs/openid-connect-backchannel-1_0.html) [turn3view0].
따라서 polling을 지원하고 싶다면, 그건 `Back-Channel Logout`이 아니라 **커스텀 세션 재검증 또는 heartbeat**로 보는 게 맞습니다.
### polling을 쓸 때의 의미
Polling을 도입하면 RP는 Baron SSO에 다음과 같은 식으로 주기적으로 확인합니다.
1. 현재 RP 세션이 아직 유효한지 확인합니다.
2. Baron 세션이 만료되었거나 연동 해지되었으면 RP 로컬 세션을 지웁니다.
3. 유효하면 아무 작업도 하지 않고 다음 주기까지 기다립니다.
이 방식은 다음과 같은 상황에서 유용할 수 있습니다.
- RP가 외부에서 inbound `POST`를 받기 어려운 경우
- 백채널 엔드포인트를 노출하기 어렵고, RP가 outbound 요청만 가능할 때
- 로그아웃 즉시성보다 단순한 운영을 우선할 때
하지만 이 방식의 한계도 분명합니다.
- 로그아웃이 실시간이 아니라 주기 지연을 가집니다.
- 요청량이 늘어납니다.
- RP가 자체적으로 polling 스케줄과 실패 복구를 관리해야 합니다.
### polling 기반 로직 구성 예시
polling을 선택한다면 보통 아래 구조로 갑니다.
```mermaid
sequenceDiagram
autonumber
participant RP as RP
participant Baron as Baron SSO
participant Store as RP Session Store
loop 주기적 확인
RP->>Baron: 세션 유효성 조회 요청
Baron-->>RP: 유효 / 무효 응답
alt 유효
RP->>RP: 로컬 세션 유지
else 무효
RP->>Store: session destroy
RP->>RP: 세션 매핑 제거
end
end
```
권장 구성은 다음과 같습니다.
1. 로그인 시 `sid` 또는 `sub`를 RP 세션에 저장합니다.
2. 주기 작업이 Baron의 세션 유효성 또는 사용자 프로필 확인 API를 호출합니다.
3. Baron 세션이 무효면 RP가 로컬 세션을 삭제합니다.
4. 민감한 화면 진입 전에도 한 번 더 재검증할 수 있습니다.
이 방식은 `backchannel logout`과는 별개로 설계하는 것이 좋습니다.
## 공통 시퀀스
아래 흐름은 세 RP에 공통입니다.
```mermaid
sequenceDiagram
autonumber
participant Baron as Baron SSO
participant RP as RP
participant JWKS as Baron Back-Channel JWKS
participant Store as RP Session Store
Baron->>RP: POST /backchannel-logout\nlogout_token=<jwt>
RP->>RP: logout_token 추출
RP->>JWKS: JWKS로 서명 검증
JWKS-->>RP: public key
RP->>RP: iss / aud / events / nonce / jti 검증
RP->>RP: sid 또는 sub로 세션 탐색
RP->>Store: session destroy
Store-->>RP: 삭제 완료
RP->>RP: 세션 매핑 제거
RP-->>Baron: 200 OK
```
## 공통 전송 규칙
Baron SSO에서 RP로 보내는 형식은 동일합니다.
| 항목 | 값 |
| --- | --- |
| HTTP method | `POST` |
| Path | `/backchannel-logout` |
| Content-Type | `application/x-www-form-urlencoded` |
| Body | `logout_token=<jwt>` |
| 검증 JWKS | `/api/v1/auth/backchannel/jwks.json` |
전송 로직은 Baron 쪽에서 공통으로 처리됩니다.
- [`backend/internal/service/backchannel_logout_service.go`](/home/kyy/workspace/baron-sso/backend/internal/service/backchannel_logout_service.go)
- [`backend/internal/handler/auth_handler.go`](/home/kyy/workspace/baron-sso/backend/internal/handler/auth_handler.go)
## RP별 차이
세 RP는 모두 `logout_token`을 받아 검증하고 세션을 지운다는 점은 같습니다.
다만 세션이 만들어지는 시점과 저장 방식이 다릅니다.
| 항목 | PKCE RP | server-side-app RP | headless login RP |
| --- | --- | --- | --- |
| 로그인 성격 | Authorization Code + PKCE | confidential client | PKCE 기반 custom login UI |
| 백채널 수신 endpoint | 필요 | 필요 | 필요 |
| 세션 저장 구조 | 앱 서버/BFF/브라우저 연동에 따라 다양 | 서버 세션 중심 | headless 로그인 이후 로컬 세션 바인딩 |
| `sid/sub` 매핑 | callback 이후 저장 | callback 이후 저장 | login 성공 이후 저장 |
| 세션 파기 방식 | 매핑된 session id 삭제 | 매핑된 session id 삭제 | 매핑된 session id 삭제 |
| 차이의 핵심 | 서버 endpoint가 없으면 처리 불가 | 서버 세션 구조와 잘 맞음 | 로그인 진입점만 다르고 로그아웃 처리는 공통 패턴 |
## RP별 처리 설명
### PKCE RP
PKCE RP는 브라우저 기반 로그인 흐름을 사용하지만, 백채널 로그아웃을 받으려면 **반드시 서버 endpoint**가 있어야 합니다.
이유는 Baron이 브라우저가 아니라 RP 서버로 직접 `POST`를 보내기 때문입니다.
처리 순서:
1. callback 이후 `sid` 또는 `sub`를 RP 세션과 바인딩합니다.
2. Baron이 `POST /backchannel-logout`를 보냅니다.
3. RP가 `logout_token`을 검증합니다.
4. `sid` 우선, 실패 시 `sub`로 세션을 찾습니다.
5. 세션 스토어에서 해당 세션을 삭제합니다.
주의:
- 순수 frontend-only PKCE 앱은 백채널 로그아웃을 직접 받을 수 없습니다.
- 서버나 BFF가 있어야 합니다.
### server-side-app RP
server-side-app RP는 confidential client이므로, 서버 세션 구조와 백채널 로그아웃이 자연스럽게 맞습니다.
처리 순서:
1. OIDC Authorization Code 로그인과 callback을 처리합니다.
2. callback 이후 `sid` 또는 `sub`를 서버 세션과 바인딩합니다.
3. Baron이 `POST /backchannel-logout`를 보냅니다.
4. RP가 `logout_token`을 검증합니다.
5. 세션 매핑을 찾아 직접 파기합니다.
이 유형은 PKCE보다 세션 관리가 명확해서 문서화와 운영이 단순합니다.
### headless login RP
headless login은 **별도의 로그아웃 타입이 아니라 PKCE 계열의 로그인 변형**입니다.
즉, 로그인 시에는 custom login UI가 있고 RP backend가 headless login API를 호출하지만, 백채널 로그아웃은 결국 동일한 패턴으로 처리합니다.
처리 순서:
1. headless login 성공 후 `sid` 또는 `sub`를 RP 세션에 바인딩합니다.
2. Baron이 `POST /backchannel-logout`를 보냅니다.
3. RP가 `logout_token`을 검증합니다.
4. `sid` 또는 `sub`로 세션을 찾습니다.
5. 세션 스토어에서 해당 세션을 삭제합니다.
핵심은 로그인 진입점만 다르고, 로그아웃 처리 패턴은 PKCE RP와 같습니다.
## 공통 검증 규칙
RP는 아래 항목을 검증해야 합니다.
1. JWT 서명 검증
2. `iss`가 Baron OIDC issuer와 일치
3. `aud`에 현재 RP `client_id` 포함
4. `iat` 존재
5. `jti` 존재
6. `events``http://schemas.openid.net/event/backchannel-logout` 포함
7. `nonce`가 없어야 함
8. `sid` 또는 `sub`가 있어야 함
권장 사항:
- `jti` replay 방지
- 시계 오차 허용
- 검증 실패 시 `400`
## 세션 파기 규칙
`Back-Channel Logout`은 현재 브라우저 요청의 `req.session.destroy()`만으로는 부족합니다.
반드시 세션 저장소에서 실제 세션 id를 찾아 직접 파기해야 합니다.
권장 우선순위:
1. `sid`로 탐색
2. `sid`가 없거나 매칭 실패 시 `sub`로 fallback
## 공통 테스트 포인트
1. RP 로그인 후 `sid/sub -> sessionId` 매핑이 생성되는지 확인
2. Baron이 `POST /backchannel-logout`를 실제로 보내는지 확인
3. RP가 `logout_token`을 검증하는지 확인
4. 세션 스토어에서 세션이 삭제되는지 확인
5. 동일한 `logout_token` 재전송 시 replay 방지가 동작하는지 확인
## 관련 문서
- [`docs/pkce-backchannel-logout-guide.md`](/home/kyy/workspace/baron-sso/docs/pkce-backchannel-logout-guide.md)
- [`docs/server-side-app-backchannel-logout-guide.md`](/home/kyy/workspace/baron-sso/docs/server-side-app-backchannel-logout-guide.md)
## 참고 구현
- [`backend/internal/service/backchannel_logout_service.go`](/home/kyy/workspace/baron-sso/backend/internal/service/backchannel_logout_service.go)
- [`backend/internal/handler/auth_handler.go`](/home/kyy/workspace/baron-sso/backend/internal/handler/auth_handler.go)