9.3 KiB
Server-Side App RP Back-Channel Logout 구현 가이드
이 문서는 Baron SSO와 연동하는 server-side-app RP가 Back-Channel Logout을 지원하려고 할 때 필요한 구현 기준을 정리합니다.
목적
server-side-app RP는 confidential client로 동작하면서, Baron SSO의 원격 세션 종료 이벤트를 받아 RP 로컬 세션을 즉시 정리할 수 있어야 합니다.
즉, server-side-app RP는 다음 둘을 모두 구현해야 합니다.
- OIDC Authorization Code 로그인과 callback 처리
logout_token을 수신하는Back-Channel Logout URI
적용 대상
이 가이드는 다음 경우를 대상으로 합니다.
server-side-app타입 RP- confidential client
client_secret_basic또는client_secret_post를 사용하는 RP- 자체 서버 세션 또는 BFF 세션을 보유하는 RP
다음 경우는 이 가이드의 직접 대상이 아닙니다.
- 순수 frontend-only SPA
- public client 기반 PKCE 앱
devfront 등록 기준
server-side-app RP는 devfront에서 아래 항목을 등록합니다.
Type:server-side-appRedirect URI: RP callback URLBack-Channel Logout URI: RP 서버 endpoint- 필요 시
SID Claim Required
예시:
Type: server-side-app
Redirect URI: http://localhost:4444/callback
Back-Channel Logout URI: http://172.16.9.208:4444/backchannel-logout
SID Claim Required: off
주의:
Back-Channel Logout URI는 브라우저 기준 주소가 아니라 Baron backend가 실제로 접근 가능한 주소여야 합니다.- Docker 환경에서는
localhost가 backend 컨테이너 자신을 가리킬 수 있으므로, 필요하면 사설 IP 또는 Docker 서비스명을 사용해야 합니다.
구현 요구사항
server-side-app RP는 최소한 아래를 구현해야 합니다.
1. confidential client 구성
RP는 일반적으로 아래 중 하나의 인증 방식을 사용합니다.
client_secret_basicclient_secret_post
즉 token 교환 시:
client_idclient_secret
가 함께 사용됩니다.
PKCE와 달리 code_verifier, code_challenge는 필수가 아닙니다.
2. 로그인 후 세션 매핑 저장
RP는 callback 이후 아래 정보 중 하나 이상을 로컬 세션과 연결해야 합니다.
sid -> rpSessionIdsub -> rpSessionId
권장 순서는 다음과 같습니다.
sid를 우선 저장sub도 함께 저장- 한 사용자가 여러 브라우저 세션을 가질 수 있으므로
1:N구조를 가정
예시:
sid: 796f5cf7-37e7-494b-9b4c-26cc0c217a6a
sub: 8150cb83-a905-4b50-bdcf-d22046ecdc30
rpSessionId: DqKlQ8MbsGnn_jfOus1k03MFRDpuXCrj
3. POST /backchannel-logout endpoint
RP는 Baron이 서버 간으로 호출할 endpoint를 제공해야 합니다.
예:
POST /backchannel-logout
Content-Type: application/x-www-form-urlencoded
Body: logout_token=<jwt>
RP는 이 endpoint에서:
logout_token존재 여부 확인- JWT 서명 및 claim 검증
sid또는sub로 로컬 세션 탐색- 세션 스토어에서 직접 세션 파기
- 성공 시
2xx응답
을 수행해야 합니다.
4. logout_token 검증
RP는 Baron이 노출하는 Back-Channel Logout JWKS로 logout_token을 검증해야 합니다.
현재 Baron의 JWKS endpoint 예시는 다음과 같습니다.
GET /api/v1/auth/backchannel/jwks.json
검증 필수 항목:
- JWT 서명 검증
iss가 Baron OIDC issuer와 일치aud에 현재 RPclient_id포함iat존재jti존재events에http://schemas.openid.net/event/backchannel-logout포함nonce가 없어야 함sid또는sub가 있어야 함
추가 권장 항목:
jtireplay 방지 캐시- 시계 오차 허용 범위 설정
- 검증 실패 시
400
세션 종료 기준
권장 순서
sid로 매칭 시도- 매칭 실패 시
sub로 fallback
이 기준은 SID Claim Required 정책에 따라 달라집니다.
SID Claim Required = true
logout_token에sid가 있어야만 처리subfallback 금지sid중심 세션 모델을 운영하는 RP에 적합
SID Claim Required = false
sid가 있으면 우선 사용sid매칭이 안 되거나sid가 없어도sub로 fallback 가능- 실제 운영에서는 이 모드가 더 유연할 수 있음
세션 파기 방식
Back-Channel Logout에서는 현재 브라우저 요청의 req.session.destroy()로는 부족합니다.
반드시 세션 스토어에서 session id를 찾아 직접 파기해야 합니다.
예:
store.destroy(rpSessionId)
필수 조건:
- 로그아웃 대상 세션 ID를 매핑 테이블에서 찾을 수 있어야 함
- 이미 삭제된 세션은 idempotent success 처리
권장 로그 항목
RP는 아래 정도의 로그를 남기는 것을 권장합니다.
- 요청 수신
- 토큰 검증 성공/실패
sid,sub,jti- 매칭된
rpSessionId목록 - 세션 파기 성공/실패 수
예시:
[백채널 로그아웃] 요청 수신
[백채널 로그아웃] 토큰 검증 성공
[백채널 로그아웃] 세션 탐색 결과
[백채널 로그아웃] 세션 파기 완료
[백채널 로그아웃] 처리 완료
주의:
- raw
logout_token전체를 로그에 남기지 않습니다. - access token, refresh token, cookie raw value도 남기지 않습니다.
테스트 체크리스트
기본 성공 시나리오
- server-side-app RP 로그인
- callback 후
sid/sub -> rpSessionId매핑 생성 확인 - UserFront에서
세션 종료 - Baron이 RP의
Back-Channel Logout URI로 POST - RP가
logout_token검증 성공 - RP 세션 파기 성공
- 보호 페이지 접근 시 비로그인 상태 확인
확인 포인트
- devfront에
Back-Channel Logout URI가 실제 저장됐는가 - Baron backend가 해당 URI에 실제로 도달 가능한가
- RP 로그에
요청 수신과토큰 검증 성공이 찍히는가 - 세션 스토어에서 실제 세션이 삭제됐는가
SID Claim Required=true일 때와false일 때 결과가 의도대로 다른가
구현 예시 구조
Node.js/Express 기준 최소 구조 예시는 다음과 같습니다.
GET /login
GET /callback
GET /profile
GET /logout
POST /backchannel-logout
내부 저장 예시:
sidToSessionIds: Map<string, Set<string>>
subToSessionIds: Map<string, Set<string>>
sessionIdToBinding: Map<string, { sid: string, sub: string }>
실제 분리 예시는 아래 데모 코드를 참고할 수 있습니다.
- 백채널 로그아웃 모듈:
/home/kyy/workspace/baron-sso-server-side-demo/backchannel-logout.js - 데모 앱 엔트리포인트:
/home/kyy/workspace/baron-sso-server-side-demo/app.js
이 데모는:
- callback 이후
registerSessionBinding()으로sid/sub -> sessionId를 등록 POST /backchannel-logout에서handleBackchannelLogout를 그대로 연결- 로컬
/logout또는 세션 정리 시removeSessionBinding()호출
구조로 동작합니다.
자주 생기는 문제
1. localhost로는 안 되는데 입력은 저장됨
입력 validation을 통과하는 것과 Baron backend가 실제로 그 주소에 도달하는 것은 다릅니다.
예:
http://localhost:4444/backchannel-logout
이 값은 backend 컨테이너 기준으로는 자기 자신을 가리킬 수 있습니다. Docker 환경에서는 Docker 서비스명 또는 사설 IP를 사용해야 할 수 있습니다.
2. sid가 로그인 시 값과 다름
실제 운영에서는 logout_token.sid가 RP가 저장한 sid와 항상 같다고 가정하면 안 됩니다.
따라서:
sid우선subfallback
구현을 권장합니다. 다만 보안 정책상 SID Claim Required=true를 선택한 경우에는 fallback 없이 sid만 사용해야 합니다.
3. client_secret 또는 auth method가 잘못되어 callback에서 실패함
server-side-app은 confidential client이므로 아래 값이 정확해야 합니다.
client_idclient_secrettoken_endpoint_auth_methodredirect_uri
이 중 하나라도 다르면 authorization code 교환 단계에서 실패할 수 있습니다.
시퀀스 다이어그램
sequenceDiagram
autonumber
participant Browser as 브라우저
participant RP as Server-Side RP
participant Baron as Baron SSO
participant Store as 세션 스토어
Browser->>RP: GET /login 호출
RP->>Browser: Baron authorize endpoint로 리다이렉트
Browser->>Baron: Authorization Code 로그인
Baron->>Browser: /callback?code=... 으로 리다이렉트
Browser->>RP: GET /callback 호출
RP->>Baron: client_secret 포함 token 요청
Baron-->>RP: ID Token / Access Token 반환
RP->>Store: RP 세션 생성
RP->>RP: registerSessionBinding(sessionId, sid, sub)
RP-->>Browser: 로그인 완료 응답
Browser->>Baron: UserFront 또는 연동 서비스에서 세션 종료
Baron->>RP: POST /backchannel-logout (logout_token)
RP->>Baron: Back-Channel JWKS로 logout_token 검증
Baron-->>RP: 서명 / issuer / audience 검증 기준 제공
RP->>RP: sid 또는 sub로 sessionId 탐색
RP->>Store: destroy(sessionId)
RP->>RP: removeSessionBinding(sessionId)
RP-->>Baron: 200 OK
Browser->>RP: GET /profile 호출
RP-->>Browser: 루트 리다이렉트 또는 비로그인 응답