62 KiB
title, source_type, status, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, branch, parent_branch, related_projects, tags, created, target_merge, status_label, contract_packet_sha256
| title | source_type | status | id | kind | project | work_item | inherits | refines | overrides | depends_on | contract_packet | branch | parent_branch | related_projects | tags | created | target_merge | status_label | contract_packet_sha256 | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| branch / feature-keycloak-internal-spa-direct-google-federation (P2B Internal SPA + Resource Server + Google federation) | branch-note | raw | BR-KEYCLOAK-CHILD-028FAA28 | branch-child | keycloak-patterns-overview | WI-KEYCLOAK-PATTERNS-OVERVIEW-020 |
|
1 | feature-keycloak-internal-spa-direct-google-federation | feature-keycloak-patterns |
|
|
2026-05-25 | in-progress | 378fb324f192fa41f903d9d9158a9ce7318aa645769b57283fdd8de553efd5af |
branch: feature-keycloak-internal-spa-direct-google-federation (P2B — Internal SPA + Resource Server + Google federation)
Layer:
raw/branch-notes/— root raw/branch-notes/feature-keycloak-patterns의 sub-branch. P2B = P2A (내부 배치 SPA + Resource Server, public client, Authorization Code + PKCE) + Google IdP brokering. 비교축:
- vs P2A (raw/branch-notes/feature-keycloak-internal-spa-direct-no-google): Google federation 추가 시 흐름·코드 변화.
- vs P1B (raw/branch-notes/feature-keycloak-edge-forwardauth-google-federation): brokering 구조는 동일, 단 SPA가 token을 직접 보유 (XSS surface 차이).
본 노트의 역할 (2026-07-17 /branch-spec 정리): P2B 는 구성(composition) 허브다. brokering 의 개별 관심사(First Broker Login Flow · claim/attribute mapping · Google client 등록 · 3-leg trust)는 각각 owner 브랜치가 소유하며, 본 노트는
rules/consistency-contract.md의 Reference-Only 규약에 따라 포인터 + 1줄 요약으로만 인용한다. 본 노트가 직접 소유하는 결정은 D5 · D8 · D9 · D10 이다.
부모 (필수)
raw/branch-notes/feature-keycloak-patterns
브랜치 계약 패킷
- 생성 시 프로젝트 개정:
1 - 패킷 스키마:
contract_packet: 1 - 완료 조건: project governance hub가 AP1~AP4 taxonomy와 child progress index를 유지한다
상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
DEC-KEYCLOAK-PATTERNS-OVERVIEW-AUTH-TAXONOMY-001@1 |
canonical 분류축은 AP1~AP4 인증 통합 아키텍처와 cross-cutting 변형이다 | SPA Direct와 Google federation의 조합을 AP1 및 brokering cross-cutting 변형으로 분류한다 | raw/project-notes/keycloak-patterns-overview |
브랜치 지역 결정
기존 branch-local 결정은 아래
## Decision Evidence Map / 결정-근거 매핑의 D-row가 소유하며 이 packet에서 복제하지 않는다.
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|
선언한 예외
| Override ID | Overrides | Reason | Approval | Status |
|---|
없음.
목표
P2A에 Google federation을 추가했을 때 흐름이 어떻게 바뀌는지 정확히 이해. 핵심 통찰:
SPA 입장에선 Keycloak만 통신한다. Google과 직접 통신하지 않는다. Google 통신은 Keycloak 내부(server-to-server)에서만 발생한다. SPA 코드 변경 거의 0.
이게 IdP brokering의 우아함이다. P2A에서 추가되는 건:
- Keycloak 관리자 설정 (Identity Provider 등록, Mappers 구성, First Broker Login Flow 정책)
- Google Cloud Console에서 OAuth Client 등록 (redirect URI = Keycloak의 broker endpoint)
SPA keycloak-js 초기화 코드, 백엔드 Resource Server JWT validation 코드, audience/issuer 확인 로직은 그대로.
면접 질문: "Google 로그인이 붙으면 SPA 코드 어디가 바뀌나요?" → "거의 안 바뀝니다. Keycloak이 Google을 자기 안으로 broker하기 때문에 SPA가 보는 token은 여전히 Keycloak token입니다. 변경 지점은 Keycloak 관리자 설정이고, 운영 부담은 사용자 매핑(First Broker Login Flow)과 claim mapping에 있습니다."
- 이슈:
- PR:
범위
포함 범위
- P2B 구성(composition) 자체 — P2A + Google brokering 을 세우는 순서와 각 단계의 owner 브랜치 배선 (§구현 가이드 §1).
- P2A→P2B 변경량(zero-change) 논증 — 어느 레이어가 안 바뀌는지의 검증 지점 (§구현 가이드 §2, D8).
- federation 축 ⟂ token 보유 축의 직교성 논증 — brokering 비용은 P1B/P2B 동일, 차이는 브라우저 token 보유뿐 (D9).
- Keycloak Identity Broker SPI 미사용 결정 — 구성요소 결정 (D5).
- P2B 배포 realm 전제의 선언 — 학습 realm 의 SMTP 미설정 사실과 그로 인한 Verify-Existing-Account 폴백 (D10). owner 가 "배포 realm 사실"로 범위 밖에 둔 결정 변수를 hub 가 소유.
- 3-leg trust / claim mapping / First Broker Login 의 배선과 인용 — 정책 자체는 owner 브랜치 소유, 본 노트는 조립만.
제외 범위
의도적으로 제외. 아래는 모두 owner 브랜치가 소유 — 본 노트는 결정하지 않고 consume 한다 (Reference-Only).
- First Broker Login Flow 의 authenticator 구성·AutoLink 정책 → raw/branch-notes/feature-keycloak-first-broker-login-flow D1 · D2 · D4 소유.
- Google claim → Keycloak attribute 매핑 · Sync Mode → raw/branch-notes/feature-keycloak-google-claim-attribute-mapping D3 · D4 소유.
- link key (
subvsemail) 및 계정 탈취 시나리오 → raw/branch-notes/feature-keycloak-account-linking-sub-vs-email D1 소유. - Google Cloud OAuth client 등록 · scope ·
trustEmail· 환경 분리 → raw/branch-notes/feature-keycloak-idp-brokering-google-client D2 · D4 · D6 · D7 소유. - SPA token 저장 위치 (메모리/cookie/localStorage) → raw/branch-notes/feature-keycloak-spa-token-storage-tradeoff D1 · D2 소유.
- BFF 패턴 실 구현 → raw/branch-notes/feature-keycloak-bff-vs-spa-direct D2 소유 (
documented-only유지). hdclaim → role 매핑 (RBAC) → raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role D3 소유.- P2B 의 실제 코드 구현 — 본 프로젝트는 학습용 문서·다이어그램 단계. 코드 repo 부재 (§Audit & Findings
NO_CODE_REPO).
근거 (필수, 최소 1개+)
본 sub-branch의 P2B (Internal SPA + Google federation) 채택 근거. 상세 대안 비교는 §외부 근거 / 대안 조사 참조.
| Source | 정당화하는 결정 |
|---|---|
| raw/official-docs/keycloak-identity-brokering-overview-official | Keycloak Identity Broker overview — Google IdP brokering 채택 근거 (D8) |
| raw/official-docs/keycloak-first-broker-login-flow | First Broker Login Flow — 사용자 매핑 시점 결정 근거 (D2, 위임) |
| raw/official-docs/google-oidc-discovery-spec | Google OIDC discovery — Google IdP 표준 동작 근거 |
| raw/official-docs/keycloak-identity-provider-mappers | IdP Mappers — claim mapping 근거 (D6, 위임) |
| raw/official-docs/keycloak-identity-broker-spi | Custom Identity Broker SPI — 대안 (현재 unused, D5) |
| raw/official-docs/oauth2-browser-based-apps-ietf-draft | (2026-07-17 추가) IETF BCP — BFF > Token-Mediating > Browser-based client 보안 순서. D9 (P2B 의 token 보유 수용) 의 공식 근거 |
| raw/official-docs/owasp-html5-storage-xss-spa | (2026-07-17 추가) 단일 XSS 로 storage 전량 탈취 — D9 의 위협 모델 근거 |
| raw/official-docs/keycloak-first-broker-login-verify-authenticators-official | (2026-07-17 추가) AutoLink = 별도 opt-in dangerous authenticator (공식 WARNING). 본 노트 2026-05-25 prose 의 정정 근거 |
| raw/official-docs/keycloak-identity-provider-sync-mode-official | (2026-07-17 추가) Sync Mode import/force 정의. D3 위임처의 근거 |
| raw/official-docs/spring-security-resource-server-jwt | Resource Server 의 issuer-uri 검증 — D7 (위임) 의 근거 |
외부 근거 / 대안 조사 (2026-05-25 — P2B Internal SPA + Google IdP Brokering)
본 sub-branch의 P2A + Google IdP Brokering 채택에 대한 외부 source. P2A에 federation을 추가하는 방식 비교.
- 채택 결정 (Keycloak Identity Brokering + Identity Provider Mappers + First Broker Login Flow):
- raw/official-docs/keycloak-identity-brokering-overview-official — Keycloak Identity Broker overview
- raw/official-docs/keycloak-first-broker-login-flow — First Broker Login Flow (사용자 매핑 시점)
- raw/official-docs/google-oidc-discovery-spec — Google OIDC discovery (issuer, jwks_uri, claims)
- raw/official-docs/keycloak-identity-provider-mappers — IdP Mappers (Google claim → Keycloak attribute/role)
- raw/official-docs/keycloak-identity-broker-spi — Custom Identity Broker SPI (custom IdP 작성 시)
- 검토한 대안:
- 대안 1: Google OIDC 직접 (Keycloak 우회) — SPA가 Google OIDC
accounts.google.com에 직접 요청. 장: Keycloak 운영 부담 0 / 단: 다중 IdP(예: GitHub, SAML) 통합 시 SPA 코드 분기 폭증. - 대안 2: AWS Cognito User Pools + Google federation — AWS Cognito가 broker 역할. 장: managed / 단: vendor lock-in.
- 대안 3: Auth0 social connections — Auth0가 Google + Facebook + GitHub 등 통합. 장: 운영 부담 최소 / 단: 비용 + vendor lock-in.
- 대안 4: Firebase Authentication — Google 자사 IdP managed. 단: Firebase 종속.
- 대안 5: SAML federation (Google Workspace SAML) — 엔터프라이즈 환경. 그러나 일반 사용자 Google 계정에는 OIDC가 표준.
- 대안 1: Google OIDC 직접 (Keycloak 우회) — SPA가 Google OIDC
- 비교 핵심: IdP Brokering의 가치는 P1B와 동일하지만, SPA 컨텍스트에서 특히 중요: SPA 코드는 Keycloak만 알면 되고, "Sign in with Google" 버튼은 Keycloak 로그인 화면이 제공 → SPA가 IdP 종류를 모름. Claim mapping으로 Google의
email,picture,name→ Keycloak attribute / role 매핑 가능. 3-leg trust (Browser ↔ Keycloak ↔ Google) — 각 단계 검증 필요. First Broker Login Flow의 default Auto-Link은 보안 위험 (P1B와 동일) — Confirm Link Existing Account로 변경.
정정 (2026-07-17) — 위 "비교 핵심" 마지막 문장(
default Auto-Link은 보안 위험 … Confirm Link Existing Account로 변경)은 사실과 다르다. OOTB 기본 경로는 이미Handle Existing Account(Confirm Link)이며,Automatically Set Existing User(AutoLink)는 기본값이 아니라 관리자가 별도로 추가하는 opt-in dangerous authenticator다 — 공식 WARNING:raw/official-docs/keycloak-first-broker-login-verify-authenticators-official.md#KC-FBLVERIFY-C4. 따라서 "AutoLink → Confirm Link 로 변경"이라는 조치는 불필요하며, 실제 결정은 "AutoLink 를 추가하지 않는다"이다. 권위 있는 서술은 owner 노트 raw/branch-notes/feature-keycloak-first-broker-login-flow D1 — AutoLink 미사용/DISABLED, OOTB 기본은 Confirm Link. 원문은 학습 이력 보존을 위해 verbatim 유지한다(덮어쓰지 않음). 상세는 §Audit & FindingsCONTRADICTION-1.
TODO
- P2A sub-branch 작성 후 코드 변경량 정량 비교 (실 LoC diff) — 등급:
planned - Keycloak admin UI에서 Google IdP 등록 스크린샷 캡처 (학습 자료) — 등급:
planned - First Broker Login Flow custom (비밀번호 확인 후 link) authenticator 설정 절차 정리 — 등급:
planned - XSS 시 token 탈취 시나리오 정리 (P1B와의 본질적 차이) — 등급:
planned - Mapper 세부 종류표 공식 문서 재확인 (
needs-confirmation해소) — 등급:planned - (2026-07-18 정합) attribute default
IMPORT와 role mapper overrideFORCE의 적용 계층을 owner 문서에서 분리하고 본 hub는 포인터만 유지 — 등급:documented-only - (2026-07-17 추가, 우선)
CVE-2026-9087공식 advisory 를wiki-source-summarizer로raw/official-docs/에 아카이브 — 현재 sibling 노트 2곳의 자기 보고만 있어 §구현 가이드 §1 0단계가needs-confirmation게이트에 머묾. 아카이브 후 영향/patch 버전을 FACT 로 승격 — 등급:needs-confirmation - (2026-07-17 추가) P1B ↔ IETF draft BFF 정의 매핑 검증 (D9 의 "P2B < P1B" 를 표준 권위로 말할 수 있는지) — 등급:
planned - (2026-07-17 추가) CSRF /
state방어의 owner 지정 — draft §6.3.2 가 browser-based client 에 CSRF 방어 MUST 를 요구하고 근거 raw 가 "P2 sub-branch 에서 별도 검증"으로 지목하는데, P2A·P2B 어느 노트도 Decision 으로 소유하지 않음(P2A 는 미체크 체크리스트 항목뿐).#OAUTH-BBA-C3의 허용이 조건부이므로 D9 의 전제이기도 함 — 등급:planned - (2026-07-18 정합) P2A D4는 본 노트 D8 pointer로 전환해 zero-change owner를 본 hub 하나로 고정 — 등급:
documented-only
진행 중 메모
- 2026-07-17 (
/branch-spec채움 pass) — 본 노트는 2026-05-25 작성분으로, 이후 2026-07-14~16 에 brokering 의 개별 관심사를 다루는 owner 브랜치들이 대거 작성되며 본 노트의 결정 대부분이 owner 를 획득했다. 따라서 이번 pass 의 핵심 작업은 새 결정을 추가하는 것이 아니라 재진술(restatement)을 Reference-Only 포인터로 강등하는 것이었다 (rules/consistency-contract.mdSingle-Owner). - 자동조사(
wiki-decision-researcher) 미실행 — 본 노트의needs-confirmation결정들이 기다리던 근거가 이미 raw 에 아카이브되어 있었다(2026-07-14~16 수집분:oauth2-browser-based-apps-ietf-draft·keycloak-identity-provider-sync-mode-official·keycloak-first-broker-login-verify-authenticators-official·keycloak-identity-provider-trust-email-official). 새 조사 대신 기존 아카이브 재앵커링으로 해소 — 조사 0건, deferred 0건. - D9 가 이번 pass 의 최대 수확 —
OAUTH-BBA-C4(IETF BCP 가 BFF → Token-Mediating → Browser-based Client 를 보안 감소 순서로 명시) 는 그동안 "XSS 위험이 높다"는 정성적 서술에 머물던 P2B vs P1B 비교에 official-standard 등급의 순서 근거를 부여한다. 2026-05-25 시점엔 이 source 가 없었다. - 코드 repo 가 없으므로 §구현 가이드는 코드 명세가 아니라 admin 구성 절차 + owner 브랜치 배선 순서로 작성했다. 모든 항목 등급은
documented-only또는planned.
결정 사항 (decisions)
외래 결정은 Reference-Only로 유지한다. 과거 상세 결정문은 각 owner의 history에서 추적한다.
- raw/branch-notes/feature-keycloak-first-broker-login-flow D4 — 현재 정책은
email_verified=false전체 hard-reject가 아니라 silent auto-link 차단이다. → D1 - raw/branch-notes/feature-keycloak-first-broker-login-flow D2 — 기존 계정 link는 Confirm Link 소유증명을 거친다. → D2
- raw/branch-notes/feature-keycloak-google-claim-attribute-mapping D3 — IdP-level profile attribute default는
IMPORT다. raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role D1의FORCE는 role mapper override다. → D3 - raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role D3 — 학습 단계에서
hdrole 제한을 적용하지 않는다. → D4 - 2026-05-25: Keycloak SPI 미사용. Google built-in social provider + Mappers로 충분. 커스텀 IdP가 필요할 때 SPI 검토 (raw/official-docs/keycloak-identity-broker-spi).
- → (2026-07-17) 본 노트 OWNED 유지 — 다른 어떤 브랜치도 SPI 채택 여부를 소유하지 않음. → D5
- 2026-07-17: P2B 의 federation 은 Keycloak 레이어에서만 broker 한다 (SPA 는 Google 과 직접 통신하지 않음). 이유: 다중 IdP 확장 시 SPA 코드 분기 폭증을 차단 — brokering 의 본질적 가치. 검토한 대안: §외부 근거 대안 1~5. → D8
- 2026-07-17: P2B 는 브라우저가 token 을 보유하는 구조를 의식적으로 수용한다 (P1B/BFF 대비 공식 보안 순서상 하위). 이유: 학습 목표가 canonical OIDC+PKCE 흐름 관찰이며, federation 축과 token 보유 축은 직교하기 때문. 근거:
#OAUTH-BBA-C4. → D9
컴포넌트 다이어그램
Browser (SPA, public client)
│
│ (1) GET /index.html
│ (2) keycloak-js → Authorization Code + PKCE 시작
▼
Keycloak (Authorization Server)
│ ┌─ 사용자가 "Google 로그인" 버튼 클릭 ─┐
│ │ (3a) Keycloak이 /broker/google/login으로 redirect
│ ▼ │
│ Google OIDC (accounts.google.com) │
│ │ (3b) Google 로그인 + consent │
│ │ (3c) authorization code → Keycloak broker endpoint
│ ▼ │
│ Keycloak ↔ Google (server-to-server)│
│ - token endpoint POST │
│ - ID token signature 검증 (JWKS)│
│ - email_verified, hd 정책 검사 │
│ - First Broker Login Flow: │
│ - 기존 user 찾기 / 신규 생성 │
│ - Account Linking 결정 │
│ - IdP Mappers 적용 (claim → attribute / role)
│ └─────────────────────────────────┘
│
│ (4) Keycloak이 자체 Authorization Code 발급 → SPA로 redirect
▼
Browser (SPA)
│ (5) code → POST /token (PKCE verifier 동봉)
│ (6) Keycloak access_token(JWT) + refresh_token + id_token 수신
▼
Backend (Resource Server, Spring Boot 등)
│ (7) Authorization: Bearer <Keycloak access_token>
│ (8) JWKS로 signature 검증 + iss/aud/exp 확인
▼
200 OK
핵심: 백엔드는 Keycloak token만 본다. Google ID token은 Keycloak 안에서 검증되고 폐기된다 (필요 시 broker endpoint로 회수 가능, 그러나 본 패턴에선 사용 안 함).
토큰 교환 sequence (P2A 1-7 + brokering 삽입)
| # | From | To | Payload | 비고 |
|---|---|---|---|---|
| 1 | Browser | Keycloak /realms/{r}/protocol/openid-connect/auth |
client_id, redirect_uri, code_challenge, scope=openid email profile, state | P2A와 동일 |
| 2 | Keycloak | Browser | 로그인 페이지 (HTML) — "Sign in with Google" 버튼 포함 | IdP 등록 시 자동 노출 |
| 3a | Browser | Keycloak /realms/{r}/broker/google/login |
(사용자가 Google 버튼 클릭) | P2B 추가 |
| 3b | Keycloak | Browser | 302 → https://accounts.google.com/o/oauth2/v2/auth?... (Keycloak이 Google client_id, redirect_uri=Keycloak broker endpoint, scope, state, nonce 동봉) |
P2B 추가 |
| 3c | Browser | 로그인 + consent | P2B 추가 | |
| 3d | Browser | 302 → Keycloak /realms/{r}/broker/google/endpoint?code=... |
P2B 추가 | |
| 3e | Keycloak | Google /token |
code, client_id, client_secret (server-to-server) | P2B 추가 |
| 3f | Keycloak | Google access_token + id_token (RS256) | P2B 추가 | |
| 3g | Keycloak | Google JWKS | (캐시된 키로) ID token signature 검증 | P2B 추가 |
| 3h | Keycloak | (internal) | First Broker Login Flow 실행 → user 매핑/생성 → Mappers 적용 | P2B 추가 |
| 4 | Keycloak | Browser | 302 → SPA redirect_uri + Keycloak code |
P2A와 동일 |
| 5 | Browser (SPA) | Keycloak /token |
grant_type=authorization_code, code, code_verifier (PKCE) | P2A와 동일 |
| 6 | Keycloak | Browser | Keycloak access_token (JWT, RS256) + refresh_token + id_token | P2A와 동일 |
| 7 | Browser | Backend /api/... |
Authorization: Bearer <access_token> | P2A와 동일 |
| 8 | Backend | Keycloak JWKS | (캐시) | P2A와 동일 |
P1B와의 차이: P1B는 oauth2-proxy가 token을 보유 (브라우저에 cookie). P2B는 브라우저가 직접 token 보유. brokering 부분(3a-3h)은 둘이 동일.
장점 / 단점
vs P2A (no Google)
| 항목 | P2A | P2B |
|---|---|---|
| Google 계정으로 로그인 | ✗ | ✓ |
| SPA 코드 변경 | — | 거의 0 (button label 정도) |
| 백엔드 코드 변경 | — | 0 (여전히 Keycloak JWT만 검증) |
| 운영 부담 | Keycloak realm/client만 | Keycloak realm/client + Google Cloud OAuth + IdP Mappers + First Broker Login Flow 정책 |
| 사용자 매핑 정책 | 불필요 (Keycloak 자체 가입) | 필수 (Account Linking, email_verified 정책 등) |
| 신뢰 경계 | Browser ↔ Keycloak (2-leg) | Browser ↔ Keycloak ↔ Google (3-leg) |
| 토큰 revocation | Keycloak refresh token revoke | 동일 (Google revoke는 별개) |
vs P1B (Edge proxy + Google)
| 항목 | P1B (oauth2-proxy 패턴) | P2B (SPA Direct) |
|---|---|---|
| 브라우저의 token 보유 | ✗ (cookie session만) | ✓ (sessionStorage/메모리) |
| XSS risk | 낮음 (token이 브라우저 JS 접근 밖) | 높음 (XSS 시 token 탈취 가능) |
| backend 추가 | proxy 필요 | proxy 불필요 |
| SPA가 OIDC 처리 | 모름 (proxy가 처리) | 직접 처리 (keycloak-js 등) |
| brokering 흐름 | 동일 | 동일 |
| token revocation | proxy session 무효화 (즉시) | Keycloak refresh token revoke (access token은 만료까지 유효) |
요약: brokering 추가의 운영 비용은 P1B/P2B 모두 같다. 두 패턴 차이는 "브라우저가 token을 보느냐"이며 이는 federation과 직교한다.
(2026-07-17 근거 보강) 위 요약의 "직교" 논증은 D9 로 승격됐고, 이제 부분적으로 공식 근거를 가진다. IETF
oauth2-browser-based-appsdraft 는 세 패턴을 보안 감소 순서로 제시한다 — BFF → Token-Mediating Backend → Browser-based OAuth 2.0 Client (#OAUTH-BBA-C4, verbatim: "presented in decreasing order of security").표준이 확정한 것 vs 본 노트가 매핑한 것을 분리한다 — 아래 ②·③에 ①의 권위를 빌려주면 안 된다:
- 표준이 확정 (추상 수준): 세 패턴의 보안 감소 순서 자체 (
#OAUTH-BBA-C4). draft 는 추상 3분류를 정의하고 순서를 매길 뿐, 우리 프로젝트의 P1A~P3B 6조합을 이 분류에 배정해주지 않는다.- 본 노트의 매핑 판정 — 강함: P2B ∈ Browser-based OAuth 2.0 Client (
#OAUTH-BBA-C3: 브라우저 앱이 public client 로 모든 OAuth 책임을 지고 token 을 직접 보유 → P2B 서술과 축자 일치). 근거는 견고하나 표준이 확정해준 것은 아니다.- 본 노트의 매핑 판정 — 약함: P1B(oauth2-proxy / Traefik ForwardAuth) ∈ BFF 계열. 근거 raw 가 이 매핑을 특정해 부인한다 —
raw/official-docs/oauth2-browser-based-apps-ietf-draft.md"Does not prove": P1A/P1B/P2A/P2B/P3A/P3B 6개 구체적 조합이 이 3분류와 1:1로 정확히 대응한다는 것 — 특히 P1(oauth2-proxy/ForwardAuth)이 이 draft 의 "BFF" 정의와 정확히 같은 개념인지는 별도 확인 필요.따라서 "P2B < P1B" 는 표준 권위로 단정할 수 없다 — 그 단정은 ②와 ③ 둘 다 참이어야 성립하며, raw 는 ②·③ 모두를 1:1 대응 부인 목록에 넣었다. 두 매핑 각각을 §Claims To Verify 로 분리 검증한다.
그래도 D9 의 착수 판단은 무너지지 않는다: D9 가 실제로 요구하는 것은 "P2B 는 브라우저에 token 을 두므로 그만큼 노출을 수용한다"이고, 이는 ②(축자 일치)만으로 성립한다. P1B 와의 상대 순서는 D9 의 대안 분기("XSS 가 유의하면 P1B 로 이동")에만 필요하며 그 분기는 미검증 상태다. 또한 이 순서는 Google federation 유무를 변수로 다루지 않으므로 "직교" 주장 역시 본 노트의 추론이며 §Claims To Verify 대상이다.
claim mapping (Google → Keycloak)
- raw/branch-notes/feature-keycloak-google-claim-attribute-mapping D3/D4 — profile attribute mapping과 IdP-level default
IMPORT의 정본이다. - raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role D1/D3 — role mapper override와
hd기반 RBAC의 정본이다.
P2B composition 관점의 한 줄 불변식: Google claim은 Keycloak 내부 user/role로 정규화된 뒤, SPA와 backend에는 Keycloak이 발급한 token만 노출된다.
신뢰 경계 (3-leg trust)
- raw/branch-notes/feature-keycloak-three-leg-trust-chain D5 — hop별 검증 matrix의 정본이다.
- raw/branch-notes/feature-keycloak-three-leg-trust-chain D1 — backend는 Google token을 직접 수용하지 않고 Keycloak token/JWKS만 신뢰한다.
P2B composition 관점의 한 줄 불변식: Google↔Keycloak 검증과 Keycloak↔Backend 검증은 서로 다른 hop이며, backend의 trust anchor는 Keycloak이다.
결정-근거 매핑
P2B 는 구성 허브다. 아래 D1~D4 · D6 · D7 은 DELEGATED — owner 브랜치가 결정을 소유하고 본 노트는 Reference-Only 포인터 + 1줄 요약만 보유한다 (
rules/consistency-contract.md). 본 노트가 직접 소유하는 결정은 D5 · D8 · D9 뿐이다. Decision ID 는 2026-05-25 판과 동일하게 유지한다(외부 참조 안정성). 위임된 행도 ID 를 재사용 번호로 남긴다.
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|---|
| D1 | DELEGATED — raw/branch-notes/feature-keycloak-first-broker-login-flow D4 — silent auto-link 차단 | owner 참조 | owner 참조 | delegated |
hard-reject SPI variant는 현재 미구현 |
| D2 | DELEGATED — raw/branch-notes/feature-keycloak-first-broker-login-flow D2 — 기존 계정 link에 소유 증명 적용 | 본 realm의 SMTP 분기는 아래 D10이 조립 | owner 참조 | delegated |
owner의 lockout risk 승계 |
| D3 | DELEGATED — raw/branch-notes/feature-keycloak-google-claim-attribute-mapping D3 — IdP-level profile default IMPORT |
role freshness는 raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role D1의 mapper override | owner 참조 | delegated |
runtime override 우선순위는 needs-confirmation |
| D4 | DELEGATED — raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role D3 — 학습 단계 hd 제한 미적용 |
owner 참조 | owner 참조 | delegated |
personal account 처리는 owner 검증 대상 |
| D5 | OWNED — Keycloak Identity Broker SPI 미사용. Google built-in social provider + Mappers 로 충분 | built-in social provider 가 대상 IdP 를 지원(Google=지원)하면 SPI 미사용. 대안(SPI 작성): 프로토콜이 OIDC/SAML 이 아니거나, built-in 이 제공 못 하는 비표준 claim 처리·custom 인증 단계가 필요할 때 | raw/official-docs/keycloak-identity-brokering-overview-official.md#KC-IDP-BROKER-C1, raw/official-docs/keycloak-identity-broker-spi.md#KC-BROKER-SPI-C1, #KC-BROKER-SPI-C2 |
official-vendor-doc |
SPI 검토 trigger 의 정량 기준 부재 — "built-in 이 부족한 시점"이 학습 단계 가정에 의존 (Should-fix, 운영 전환 시 재평가) |
| D6 | DELEGATED — raw/branch-notes/feature-keycloak-google-claim-attribute-mapping D4 — profile attribute mapping | owner 참조 | owner 참조 | delegated |
link key는 raw/branch-notes/feature-keycloak-account-linking-sub-vs-email D1 |
| D7 | DELEGATED — raw/branch-notes/feature-keycloak-three-leg-trust-chain D1 — backend trust anchor는 Keycloak | owner 참조 | owner 참조 | delegated |
owner의 미검증 항목 승계 |
| D8 | OWNED — P2B 의 federation 은 Keycloak 레이어에서만 broker (SPA 는 Google 과 직접 통신하지 않음). 메커니즘: IdP 를 realm 에 등록하면 Keycloak 로그인 페이지가 버튼을 자동 제공하므로 SPA 코드가 IdP 를 몰라도 됨 | IdP 가 1개 초과로 늘어날 가능성이 있거나 IdP 종류를 앱에서 숨기고 싶으면 brokering. 대안: IdP 가 영구히 Google 1개 + Keycloak 운영 부담을 피하고 싶다 → SPA 가 Google OIDC 직접(§외부 근거 대안 1). managed 선호 → Cognito/Auth0/Firebase(대안 2~4). 경계: Hide on Login Page=ON 이면 버튼이 안 뜨고 앱이 kc_idp_hint 를 보내야 함 → 그 순간 zero-change 전제가 깨지고 자식 raw/branch-notes/feature-keycloak-federation-spa-zero-change D1 (idpHint 미사용) 의 대안 경로로 이동 |
raw/official-docs/keycloak-identity-brokering-overview-official.md#KC-IDP-BROKER-C1 (broker delegation 모델, L0/L1); (2026-07-17 보강 — L1/L2) raw/official-docs/keycloak-idp-hide-on-login-page-toggle-official.md#KC-HIDELOGIN-C2 (IdP 구성 시 로그인 페이지에 로그인 옵션으로 나타남 = zero-change 의 메커니즘, verbatim), #KC-HIDELOGIN-C1 (realm 의 등록된 모든 IdP 를 앱이 사용 가능·기본 활성 → 앱별 코드 불요), #KC-HIDELOGIN-C3 (ON 일 때만 미노출 + kc_idp_hint 대안 = 조건·경계 L2), raw/official-docs/keycloak-google-idp-setup.md#KC-GIDP-C1 (Google 등록 경로) |
official-vendor-doc (L1~L2) |
① "IdP 가 늘어난다"는 전제가 학습 프로젝트에선 가정 — 실제 다중 IdP 요구 미발생 시 대안 1 이 더 단순 (Advisory). ② Hide on Login Page 토글의 신규 등록 시 기본 상태는 KC-HIDELOGIN-C3 원문이 직접 진술하지 않음 → 기본이 ON 이면 zero-change 전제 붕괴. 자식 노트가 동일 caveat 를 needs-confirmation 으로 보유 → visual verify 필수 (§Claims To Verify) |
| D10 | OWNED (2026-07-17 신규 — hub 가 소유하는 배포 realm 사실) — P2B 학습 realm 은 SMTP 를 설정하지 않는다 → Verify Existing Account By Email 을 쓸 수 없어 Re-authentication 이 자동 폴백됨 |
학습 스택(메일 서버 없음) → SMTP 미설정 → owner 의 fork 중 "Re-authentication" 가지가 자동으로 선택됨(관리자 조치 불요). 대안: 운영/소비자 서비스로 전환해 SMTP 를 붙이면 → Verify Existing Account By Email 이 기본(ALTERNATIVE)이 되므로, password 소유 증명을 관철하려면 그때 email authenticator 를 명시 DISABLE 해야 함 → raw/branch-notes/feature-keycloak-first-broker-login-flow D2 |
raw/official-docs/keycloak-first-broker-login-verify-authenticators-official.md#KC-FBLVERIFY-C3 (Re-auth = "email authenticator 를 쓸 수 없을 때(예: realm 에 SMTP 미설정)만 쓰는" 폴백, verbatim), #KC-FBLVERIFY-C1 (Email = SMTP 시 기본) |
official-vendor-doc + documented-only (배포 사실) |
SMTP 미설정은 부재 근거: raw/branch-notes/feature-keycloak-docker-compose-stack 이 SMTP/mail 을 일절 구성하지 않음에서 도출했을 뿐, 스택 노트가 "SMTP 없음"을 명시 선언하지는 않는다 → 실 스택 기동 시 realm SMTP 설정란 확인 필요 (§Claims To Verify). owner 는 이 realm 사실을 자기 범위 밖(UNSUPPORTED_IMPL_DECISION)으로 명시 배제했으므로 hub 인 본 노트가 소유한다 |
| D9 | OWNED — P2B 는 브라우저 token 보유를 의식적으로 수용. federation 축과 token 보유 축은 직교 — brokering 비용은 P1B/P2B 동일 | 학습 목표가 canonical OIDC+PKCE 흐름 관찰이면 P2B 수용. 대안: XSS 위협이 유의(운영·민감 데이터) → P1B/BFF 계열로 이동(토큰을 브라우저 밖으로). 저장 위치 선택은 raw/branch-notes/feature-keycloak-spa-token-storage-tradeoff D1 | raw/official-docs/oauth2-browser-based-apps-ietf-draft.md#OAUTH-BBA-C4 (BFF→TMB→Browser-based 는 보안 감소 순서, verbatim), #OAUTH-BBA-C3 (browser-based client 정의), raw/official-docs/owasp-html5-storage-xss-spa.md#OWASP-HTML5-C2 (단일 XSS 로 전량 탈취) |
official-standard + official-reference |
BFF 채택 여부는 raw/branch-notes/feature-keycloak-bff-vs-spa-direct D1 이 소유 — 본 결정은 그 판정을 P2B(federation 포함)로 확장 적용한 것이며, 확장의 타당성(federation 이 위협 모델을 바꾸지 않음)은 §Claims To Verify 대상 |
구현 가이드
본 브랜치는 코드 repo 가 없다 (학습용 문서·다이어그램 단계 — §Audit
NO_CODE_REPO). 따라서 본 §는 코드 명세가 아니라 ① P2B 를 세우는 구성 순서(owner 브랜치 배선) 와 ② P2A 대비 zero-change 검증 지점 을 명세한다. 모든 항목 등급은documented-only또는planned—actually-implemented주장 없음. Out-of-scope 정제(R3): 개별 정책 detail(authenticator 토글·mapper 필드·Google client 등록 절차)은 owner 브랜치 소유이므로 본 §에 재진술하지 않는다 — 포인터만 둔다.
1. P2B 구성 순서 (owner 브랜치 배선)
Trace: D8 (Keycloak 레이어 brokering) 의 도출. 각 단계의 정책은 괄호 안 owner 브랜치가 소유하며 본 표는 순서와 의존만 명세한다.
- UNSUPPORTED_IMPL_DECISION: 단계 순서 자체(1→5)는 어느 공식 문서도 규정하지 않는다. 사용자 trade-off — Google client 를 먼저 만들어야 Keycloak 에 넣을 client_id/secret 이 생기고(2→3), IdP 를 등록해야 redirect URI 확정값이 나오는 순환 의존이 있어, "Keycloak 에서 IdP 를 먼저 생성해 redirect URI 를 얻고 → Google 에 등록 → 되돌아와 secret 입력" 순으로 끊었다. 반대 순서도 가능하나 redirect URI 를 손으로 추측해야 해서 오타 위험이 크다.
- UNSUPPORTED_DECISION (0단계의 근거 등급):
CVE-2026-9087은 raw 에 아카이브된 출처가 없다 — 현재 근거는 sibling branch-note 2곳의 자기 보고뿐이며, CLAUDE.md §11 상 note→note 전이는 근거가 아니다. 따라서 0단계는 "확인하라"는 게이트일 뿐 버전 번호를 FACT 로 단정하지 않는다(형제 노트가 적은26.3.0~26.6.1/ patched2026-06-02 PR #49513도 미검증 전언). 사용자 trade-off — 근거가 얇아도 계정 탈취 방어의 우회 라는 주장의 파급이 크므로, 검증 전까지 게이트를 열어두지 않고 닫아둔다(fail-safe). 해소: 공식 advisory 를wiki-source-summarizer로 아카이브 (§TODO).
| # | 단계 | 산출물 (다음 단계 입력) | 정책 owner (Reference-Only) | 등급 |
|---|---|---|---|---|
| 0 | Keycloak 버전 하한 확인 — 본 노트가 consume 하는 D2 의 core 방어(Confirm Link + Verify Existing Account)가 우회되지 않는 patched 버전인지 먼저 확인. 미확인 상태로 1단계 진행 금지 | 버전이 고정된 스택 | 버전 caveat 의 출처는 raw/branch-notes/feature-keycloak-idp-brokering-google-client D6 의 Open Risk (CVE-2026-9087 — cross-session verification proof 가 upstream identity 에 미결속, trustEmail=false+email 인증 상태에서도 우회 존재로 보고). 형제 raw/branch-notes/feature-keycloak-account-linking-spa-ux 도 동일 항목을 flag |
needs-confirmation ⚠️ |
| 1 | Keycloak realm 에 Google IdP 생성 → Redirect URI 확정값 확보 | https://<kc-host>/realms/<realm>/broker/google/endpoint |
raw/branch-notes/feature-keycloak-idp-brokering-google-client D1 — Admin Console Identity Providers → Add provider → Google |
planned |
| 2 | Google Cloud Console 에 OAuth Web application client 생성 + 1 의 Redirect URI 등록 | client_id, client_secret |
같은 노트 D2 (Web application client) · D3 (Redirect URI 그대로 복사, exact-match) | planned |
| 3 | Keycloak IdP 에 client_id/client_secret 입력 + scope·discovery·trustEmail 설정 |
동작하는 broker | 같은 노트 D4 (scope openid profile email 최소) · D5 (discoveryURL) · D6 (trustEmail=false) |
planned |
| 4 | First Broker Login Flow 정책 확정 (AutoLink 추가하지 않음) | 사용자 매핑 정책 | raw/branch-notes/feature-keycloak-first-broker-login-flow D1 · D2 · D3 | planned |
| 5 | IdP Mapper 구성 (attribute importer + Sync Mode) | Keycloak user attribute | raw/branch-notes/feature-keycloak-google-claim-attribute-mapping D3 (IMPORT) · D4 (4종 mapper) | planned |
검증 종료 조건: SPA 로그인 화면에 "Sign in with Google" 버튼이 자동 노출되고(3h 이후), SPA 가 받은 token 의 iss 가 Keycloak realm URL 이면 P2B 성립.
2. P2A → P2B 변경량 (zero-change 검증 지점)
Trace: D8 · D9 의 도출 + §목표/WHY 의 핵심 통찰("SPA 코드 변경 거의 0")을 검증 가능한 형태로 고정. 자식 raw/branch-notes/feature-keycloak-federation-spa-zero-change D1 (idpHint 미사용) 이 SPA 측 결정을 소유.
- UNSUPPORTED_IMPL_DECISION: "거의 0"의 정량 기준(LoC diff = 0 인지, button label 1줄인지)은 어떤 근거 문서도 규정하지 않는다. 사용자 trade-off — P2A 실 구현 부재로 diff 를 아직 못 잰다. §TODO 의 "실 LoC diff" 항목으로 이연하며, 그 전까지 "거의 0"은 주장이지 측정값이 아니다.
| 레이어 | P2A | P2B | 기대 변경량 | 검증 방법 |
|---|---|---|---|---|
SPA keycloak-js 초기화 |
init({onLoad, pkceMethod:'S256'}) |
동일 | 0 줄 | P2A/P2B 설정 파일 diff — 차이 0 이어야 함 |
| SPA 로그인 트리거 | keycloak.login() |
동일 (idpHint 미사용 → Keycloak 화면이 Google 버튼 노출) | 0 줄 | raw/branch-notes/feature-keycloak-federation-spa-zero-change D1 |
| Backend JWT validation | issuer-uri = Keycloak realm |
동일 | 0 줄 | #SSRS-JWT-C1 — issuer-uri 자동 검증 |
| Backend audience 검증 | aud = backend client |
동일 | 0 줄 | D7 위임 |
| Keycloak admin 설정 | realm + client | + IdP + Mappers + FBL flow | 변경 전량이 여기 집중 | §1 표 |
| Google Cloud Console | 없음 | + OAuth client | 신규 | §1 표 2단계 |
논증: 변경량이 전부 마지막 2행(admin/console)에 몰리고 코드 4행이 0 이면, "brokering 은 SPA 에 투명하다"는 D8 의 주장이 성립한다.
3. D5 (SPI 미사용) 의 구현 귀결
Trace: D5 (OWNED) 의 도출.
#KC-BROKER-SPI-C1·#KC-BROKER-SPI-C2는 SPI 의 존재와 용도를 규정할 뿐, 미사용 시 무엇을 하지 않아도 되는지는 규정하지 않으므로 아래는 그 대우(contrapositive).
- UNSUPPORTED_IMPL_DECISION: 없음 — 아래는 "SPI 를 안 쓴다"의 직접 귀결이며 새 메커니즘 선택이 아니다.
| 항목 | SPI 미사용 시 | SPI 채택 시 (대안) |
|---|---|---|
| 배포 산출물 | Keycloak 컨테이너 이미지 그대로 (jar 추가 없음) | provider jar 빌드 + providers/ 배치 + 재빌드 |
| Keycloak 업그레이드 | built-in provider 가 함께 유지보수됨 | SPI 인터페이스 호환성 직접 추적 |
| 구성 방법 | Admin Console 선언적 설정 (§1) | Java 코드 + 컴파일 |
엣지·실패·의존
실패·엣지 경로
| 경로 | 기대 동작 | 소유/근거 |
|---|---|---|
| 사용자가 Google consent 거부 | Google 이 error=access_denied 로 broker endpoint 회신 → Keycloak 로그인 화면 복귀 (SPA 는 code 를 못 받음). SPA 는 이 실패를 Keycloak 실패와 구분 못 함 — brokering 투명성의 대가 |
D8 의 귀결. 정확한 Keycloak 화면 동작은 needs-confirmation (§Claims To Verify) |
Google email_verified=false 계정 |
AutoLink 없이 Confirm Link 소유증명 경로로 처리해 silent auto-link만 차단. 전체 link/생성 hard-reject는 현재 미구현 | raw/branch-notes/feature-keycloak-first-broker-login-flow D4 |
| 같은 email 의 기존 local user 존재 | Confirm Link 경로 (SMTP realm 은 email 검증이 기본) — 본 노트 원문의 "비밀번호 확인" 은 기본 아님 | raw/branch-notes/feature-keycloak-first-broker-login-flow D2 |
| Google-first 가입자(비밀번호 미설정)가 Re-authentication 요구받음 | lockout 가능 — 재인증 수단 없음 | owner Open Risk 승계 (같은 노트 D2) |
personal Gmail (hd claim 부재) |
role 미부여 상태 통과 — 학습 단계에선 허용 | raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role D3 |
| Google client_secret 유출 | Google client 재생성 + secret rotation. 환경별 client 분리로 폭발반경 축소 | raw/branch-notes/feature-keycloak-idp-brokering-google-client D7 |
| Google JWKS 키 rotation | Keycloak 이 캐시 갱신 (Keycloak 책임, backend 무관) | raw/branch-notes/feature-keycloak-three-leg-trust-chain D2 (UNSUPPORTED_DECISION 상태) |
| Keycloak 버전이 patch 이전 | D2 가 위임한 core 방어(Confirm Link + Verify Existing Account)가 우회 가능 — 즉 본 노트가 "owner 가 막아준다"고 가정한 계정 탈취 경로가 실제로는 열려 있을 수 있음. 기대 동작: §구현 가이드 §1 0단계에서 차단(스택 기동 전) | raw/branch-notes/feature-keycloak-idp-brokering-google-client D6 Open Risk (CVE-2026-9087). 근거 등급 needs-confirmation — raw 출처 부재, note→note 전언 (§구현 가이드 §1 UNSUPPORTED_DECISION) |
| SPA XSS | access token 탈취 → 만료까지 유효 (즉시 revoke 불가). P2B 의 본질적 약점 | D9 · #OWASP-HTML5-C2 |
| Keycloak 다운 | Google 로그인 포함 전 인증 경로 중단 — brokering 은 Keycloak 을 단일 장애점으로 만든다 (P2A 대비 축소 없음, 단 Google 의존이 추가돼도 Keycloak 없이는 무의미) | D8 의 귀결 (Advisory) |
다른 계약 의존
본 노트는 구성 허브이므로 의존이 많다. 각 owner 의 D-row 가 바뀌면 본 노트 §DEM 의 1줄 요약이 낡는다 →
/sync수거 대상.
- raw/branch-notes/feature-keycloak-internal-spa-direct-no-google (P2A) D1 · D3 · D4 · D5 — 본 노트의 정의상 baseline (P2B ≡ P2A + brokering). §구현 가이드 §2 의 "P2A" 열 전량이 P2A 소유 결정이다: D5(PKCE S256 의무) = 1행 baseline, D3(backend =
iss+signature+exp+aud4종) = 3~4행 baseline, D1(P2A = SPA Direct 정의). 바뀌면: §2 의 zero-change 논증이 기준선째 바뀌고 D8·D9 의 전제가 흔들린다.- 2026-07-18 owner 정합 완료: zero-change invariant는 본 노트 D8이 소유하고 P2A D4는 이 행을 가리키는 Reference-Only pointer로 전환했다.
- ⚠️ 미소유 관심사 (P2 sub-branch 지시): 인용 raw 가 본 노트류를 명시 지목한다 —
raw/official-docs/oauth2-browser-based-apps-ietf-draft.md: P2(SPA-direct) 가 이 draft 의 §6.3.2 (PKCE MUST, CSRF 방어 MUST) 요건을 실제로 만족하는지 P2 sub-branch 에서 별도 검증. PKCE 는 P2A D5 가 소유하나 CSRF/state방어는 어느 노트도 Decision 으로 소유하지 않는다(P2A 는 미체크 체크리스트 항목으로만 보유) →#OAUTH-BBA-C3의 허용은 조건부이므로 D9 의 전제이기도 하다. 소유자 지정 필요 (§TODO).
- raw/branch-notes/feature-keycloak-first-broker-login-flow D1 · D2 · D4 — 사용자 매핑/링크 정책 전량 consume. 바뀌면: §DEM D1·D2, §구현 가이드 §1 4단계, §엣지 표 3~4행 영향. ⚠️ 같은 노트가 realm SMTP 사실("학습 스택은 SMTP 없이 시작 → 자동 폴백")을 무라벨 단정으로 보유 → 본 노트 D10 과 이중 주장 (
/sync수거 대상, owner 는 D10 이어야 함 — owner 노트가 realm 사실을 자기 범위 밖으로 명시 배제했으므로). - raw/branch-notes/feature-keycloak-google-claim-attribute-mapping D3 · D4 · D5 — attribute 매핑·Sync Mode consume. 바뀌면: §DEM D3·D6, §claim mapping 표 정정 주석, §구현 가이드 §1 5단계 영향.
- raw/branch-notes/feature-keycloak-idp-brokering-google-client D1
D7 — Google client 등록·scope·trustEmail consume. 바뀌면: §구현 가이드 §1 13단계 전량 영향. - raw/branch-notes/feature-keycloak-account-linking-sub-vs-email D1 — link key =
sub. 바뀌면: §claim mapping 표sub행 + D6 영향. - raw/branch-notes/feature-keycloak-three-leg-trust-chain D1 · D2 · D5 — 3-leg 검증 매트릭스 consume (자식). 바뀌면: §DEM D7, §신뢰 경계 영향.
- raw/branch-notes/feature-keycloak-federation-spa-zero-change D1 — idpHint 미사용 (자식). 바뀌면: §구현 가이드 §2 의 "SPA 로그인 트리거 0 줄" 논증이 깨짐 (idpHint 를 쓰면 SPA 코드가 바뀜 → D8 의 zero-change 주장 약화).
- raw/branch-notes/feature-keycloak-spa-token-storage-tradeoff D1 · D2 — token 저장 위치. 바뀌면: D9 의 위협 모델 전제 영향.
- raw/branch-notes/feature-keycloak-bff-vs-spa-direct D1 — SPA Direct 학습 1순위 채택. 바뀌면: D9 의 상위 전제가 무너짐 (BFF 로 이동 시 P2B 자체가 P1B 로 대체됨).
- raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role D1/D3 — role mapper override와
hd정책을 consume한다. IdP-level defaultIMPORT와 role mapper overrideFORCE는 적용 계층이 분리됐다.
검증해야 할 주장
P2B 는 문서/다이어그램 단계 (
documented-only). 실 구현 시 검증해야 할 동작:
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
(2026-07-17) 해소 — 주장이 틀렸음. #KC-FBLVERIFY-C4 의 공식 WARNING 상 AutoLink 는 기본값이 아니라 별도 opt-in dangerous authenticator. owner raw/branch-notes/feature-keycloak-first-broker-login-flow D1 이 정정 보유 |
(검증 불필요 — 전제 오류) 잔여 검증은 owner 의 Claims To Verify 로 이관: AutoLink authenticator 의 정확한 추가 위치 확인 | resolved-corrected |
|
(2026-07-17) 위임. Sync Mode 는 본 노트 소유 아님 → owner raw/branch-notes/feature-keycloak-google-claim-attribute-mapping D3 (IMPORT) 이 #KC-SYNCMODE-C3/#KC-SYNCMODE-C4 로 정의 보유 |
owner 의 Claims To Verify 로 이관 (Admin UI 드롭다운 라벨 + 실동작 캡처) | delegated |
|
| "Confirm Link Existing Account" authenticator 로 변경 시 사용자가 기존 비밀번호 입력 후에만 link 진행 | (2026-07-17 갱신) SMTP 설정 realm 은 email 검증이 기본(#KC-FBLVERIFY-C1) → "비밀번호 입력"은 email authenticator 를 DISABLE 해야 관철됨(#KC-FBLVERIFY-C2). 본 노트 원문 전제가 부정확 |
flow copy 후 email authenticator DISABLE → 같은 email local user 의 비밀번호 prompt 확인. owner 소유 — raw/branch-notes/feature-keycloak-first-broker-login-flow D2 | delegated |
Google ID token 의 email_verified claim 이 항상 존재하고 boolean (Personal vs Workspace 차이 없음) |
GOOGLE-OIDC-C5 는 nonce 만 verbatim. email_verified 자체의 always-present 보장은 본 인용에 없음 |
실 Personal Google account + Workspace account 두 가지로 federation → Keycloak Events 로그에서 claim 값 확인 | needs-confirmation |
Backend Spring Security 가 Keycloak access token 의 iss claim 으로 (Google 이 아닌) Keycloak realm URL 만 신뢰 |
SSRS-JWT-C1/C2 는 issuer-uri 자동 검증을 보장. 그러나 Google token 이 우회 경로로 backend 에 도달 가능한지는 별도 위협 모델 |
Google ID token 을 직접 backend /api/me 에 제출 → 401 응답 확인 (issuer mismatch) |
planned |
Advanced Claim to Role mapper 로 hd claim → role 매핑 시 personal account (no hd) 가 role 미부여 상태로 통과 |
KC-IDP-MAPPER-C4 는 mapper 종류 목록이 verbatim 부재. GOOGLE-OIDC-C7 은 hd 부재 처리를 직접 다루지 않음 |
Personal Gmail 로 로그인 → Keycloak user 의 realm role / attribute 확인 | needs-confirmation |
| XSS 시 SPA 의 access token 탈취 가능 (P1B 대비 P2B 의 본질적 약점) | OWASP-HTML5-C1/C2/C3 는 storage XSS 위협을 보장. memory 보관 token 도 동일 위협인지는 추가 reasoning 필요 |
의도적 XSS payload 주입 (테스트 페이지) → document.cookie 또는 window.tokenStore 접근 가능성 확인 |
planned |
(D9) federation 추가가 P2B 의 token 위협 모델을 바꾸지 않는다 (직교성) — 즉 #OAUTH-BBA-C4 의 보안 순서가 Google IdP 유무와 무관하게 성립 |
OAUTH-BBA-C4 는 세 패턴의 보안 순서를 규정하나 federation 유무를 변수로 다루지 않는다 — 직교 주장은 본 노트의 추론 |
P1B/P2B 의 위협 목록을 나란히 작성해 brokering 이 추가하는 위협(Google client_secret, 3-leg)이 token 보유 축과 독립임을 표로 대조 | planned |
| (D8) Google consent 거부 시 SPA 가 받는 최종 상태 (Keycloak 로그인 화면 복귀 vs SPA 로 error redirect) | brokering 투명성의 실패측 동작이 어느 인용에도 없음 | Google consent 화면에서 "취소" → 브라우저 최종 URL + SPA 상태 관찰 | needs-confirmation |
| (D9) P2B 가 IETF draft 의 Browser-based OAuth 2.0 Client 정의에 실제로 대응하는가 — D9 의 핵심 전제 | #OAUTH-BBA-C3 의 정의(브라우저 앱 = public client, 모든 OAuth 책임을 브라우저에서, resource server 와 직접 통신)와 P2B 서술이 축자 일치하나, 근거 raw 의 "Does not prove" 가 P2B 포함 6조합의 3분류 1:1 대응을 부인 → 매핑은 본 노트의 판정이지 표준 확정이 아님 |
draft §6.3 정의와 P2B 구성을 요건별로 1:1 대조 (public client 여부 · client credentials 부재 · token 직접 보유 · RS 직접 호출). 추가로 §6.3.2 의 PKCE MUST / CSRF 방어 MUST 충족 여부 확인 — draft 가 "P2 sub-branch 에서 별도 검증"으로 본 노트류를 명시 지목 | planned |
| (D9) P1B(oauth2-proxy / Traefik ForwardAuth) 가 IETF draft 의 BFF 정의에 실제로 대응하는가 — 이 매핑이 있어야 "P2B < P1B" 를 표준 권위로 말할 수 있음 | 근거 raw 가 명시적으로 부인: oauth2-browser-based-apps-ietf-draft.md "Does not prove" 열 — oauth2-proxy/ForwardAuth(P1A/P1B)가 draft 의 BFF 정의와 1:1 동일하다는 것 … 매핑 정합성은 별도 확인 필요. 본 노트의 추론 |
draft §6.1 의 BFF 요건(backend = confidential client · 토큰을 쿠키 세션에 보관 · 모든 요청을 프록시)을 oauth2-proxy 실동작과 1:1 대조. 프록시 요건이 어긋나면 P1B 는 BFF 가 아니라 §6.2 Token-Mediating Backend 이거나 그 외 → 순서 주장 재작성 필요 | planned |
(D8) Hide on Login Page 토글의 신규 IdP 등록 시 기본 상태가 OFF (=버튼 자동 노출) — D8 의 zero-change 메커니즘 전제 |
#KC-HIDELOGIN-C3 은 "ON 일 때만 미노출"을 규정할 뿐 기본값을 진술하지 않음. 기본이 ON 이면 SPA 가 kc_idp_hint 를 보내야 하므로 D8 의 "SPA 코드 0줄" 이 붕괴 |
Google IdP 신규 등록 직후 아무 설정도 건드리지 않은 상태로 로그인 화면 방문 → "Sign in with Google" 버튼 노출 여부 visual verify. 자식 raw/branch-notes/feature-keycloak-federation-spa-zero-change 가 동일 검증 소관 | needs-confirmation |
| (D10) P2B 학습 realm 에 SMTP 가 실제로 미설정 — D2 의 fork 가 Re-authentication 으로 자동 폴백된다는 전제 | 부재 근거로 도출: raw/branch-notes/feature-keycloak-docker-compose-stack 이 SMTP/mail 을 일절 구성하지 않음에서 추론했을 뿐, "SMTP 없음"의 명시 선언은 없음 | 스택 기동 후 Admin Console → Realm settings → Email 탭이 비어 있는지 확인 + 같은 email local user 로 링크 시도 → 비밀번호 prompt(Re-auth) 가 뜨는지 관찰 (email 검증 메일이 오면 전제 붕괴) | needs-confirmation |
(0단계) CVE-2026-9087 의 실재·영향 버전·patch 버전 |
raw 출처 부재 — sibling branch-note 2곳의 자기 보고뿐이며 note→note 전이는 근거가 아님 (CLAUDE.md §11) | 공식 advisory(NVD / Keycloak security advisory / 해당 PR)를 wiki-source-summarizer 로 raw/official-docs/ 에 아카이브 → 영향 버전·patch 를 verbatim 확보 후 §구현 가이드 §1 0단계를 FACT 로 승격 |
needs-confirmation |
Audit & Findings
2026-07-17
/branch-specpass 의 감사 결과. 자동 수정하지 않고 기록만 한다 (rules/consistency-contract.md— "적용은 항상 승인 후", "owner 문서 우선").
| ID | 코드 | 위치 | 내용 | 조치 |
|---|---|---|---|---|
| CONTRADICTION-1 | CONTRADICTION |
§외부 근거 "비교 핵심" 마지막 문장 | 본 노트: "default Auto-Link은 보안 위험 → Confirm Link 로 변경". 공식(#KC-FBLVERIFY-C4) + owner raw/branch-notes/feature-keycloak-first-broker-login-flow D1: OOTB 기본이 이미 Confirm Link, AutoLink 는 별도 opt-in |
해소됨 (2026-07-17, 사용자 판정 = 인라인 정정 마커) — 원문 verbatim 보존 + 정정 blockquote 추가. Claims To Verify 1행 resolved-corrected 로 갱신 |
| CONTRADICTION-2 | CONTRADICTION + DUAL_OWNERSHIP |
§claim mapping · raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role D1 | IdP-level default와 role mapper override를 같은 설정으로 취급했던 drift | 해소 (2026-07-18) — profile default IMPORT는 google-claim owner D3, role mapper override FORCE는 role owner D1로 적용 계층 분리. 본 hub는 pointer만 유지 |
| RESTATEMENT-1 | RESTATED_FOREIGN_DECISION |
§claim mapping · §신뢰 경계 | foreign decision 상세 복제 | 해소 (2026-07-18) — 두 섹션과 delegated D-row를 owner pointer + 한 줄 불변식으로 축소 |
| NO_CODE_REPO | (본 pass 로컬 라벨) | 전역 | keycloak-patterns 프로젝트는 코드 repo 부재 — actually-implemented 확정 불가 (src/ grep 대상 없음). ca-tmpl 은 본 브랜치와 무관한 별개 프로젝트 |
§구현 가이드를 admin 구성 절차로 작성, 전 항목 documented-only/planned 유지 |
| COVERAGE_EXEMPT | (게이트 결과) | frontmatter | governing_docs 부재 + related_projects: [keycloak-patterns] (ca-* 아님) → rules/coverage-gate.md §7 에 의해 coverage 면제 |
조치 없음 (정상) |
depth 게이트 1회차 findings (2026-07-17 branch-depth-auditor, Blocking 1
| # | 축 | 심각도 | 내용 | 해소 (2회차 반영) |
|---|---|---|---|---|
| 1 | R1 | Blocking | D8(OWNED)의 Supporting Claim 이 #KC-IDP-BROKER-C1 L0 1개뿐 — "IdP 를 붙이면 SPA 가 정말 안 바뀌나"를 되묻게 됨 |
해소 — #KC-HIDELOGIN-C2(IdP 구성 시 로그인 페이지에 옵션 자동 노출 = L1 메커니즘) · #KC-HIDELOGIN-C1 · #KC-HIDELOGIN-C3(ON 일 때만 미노출 + kc_idp_hint = L2 경계) · #KC-GIDP-C1 추가. 토글 기본 상태 caveat 는 Open Risk + Claims To Verify 로 이관 |
| 2 | R1 | Should-fix | §장점/단점 이 "P2B < P1B 는 표준의 명시적 순서"라고 단정 — 그러나 #OAUTH-BBA-C4 는 추상 3패턴 순서만 규정하고, 근거 raw 는 "oauth2-proxy/ForwardAuth ↔ BFF 매핑은 별도 확인 필요"라고 명시 부인. 노트가 "주관적 평가가 아니라"라고 써서 다음 독자의 검증을 차단 |
해소 — ①표준 확정(P2B ∈ Browser-based Client)과 ②본 노트 추론(P1B ∈ BFF)을 분리 기술 + Claims To Verify 1행 추가. 결론 방향은 ①만으로 유지됨 |
| 3 | R2 | Should-fix | D2 가 owner 의 fork(Email vs Re-auth)를 옮겨오기만 하고 P2B 가 어느 쪽인지 미판정. owner 는 realm SMTP 사실을 자기 범위 밖으로 명시 배제 → 결정 변수의 주인이 없음 | 해소 — D10 신규(OWNED): 학습 realm = SMTP 미설정 → #KC-FBLVERIFY-C3 폴백으로 Re-auth 자동. D2 선택 조건이 D10 을 참조. 단 SMTP 미설정은 부재 근거 → Claims To Verify 1행 |
| 4 | R4 | Should-fix | 버전 축 누락 — D2 의 core 방어가 CVE-2026-9087 로 우회 가능하다고 형제 2곳이 flag 하는데, 정작 배선 순서를 소유한 hub 에 버전 전제가 없음 (P2B 전문 버전 언급 0) |
해소(조건부) — §구현 가이드 §1 에 0단계(버전 하한 확인) + §엣지 표 1행 추가. 단 CVE 는 raw 출처 부재(note→note 전언) → UNSUPPORTED_DECISION 라벨 + 버전 번호 FACT 단정 회피 + advisory 아카이브 TODO(우선) |
| 5 | R1 | Advisory | §신뢰 경계 위임 고지가 owner(three-leg-trust-chain D5)의 UNSUPPORTED_DECISION 상태를 승계 표기하지 않음 — pointer 는 resolvable 하나 미지지 |
해소 — 고지에 "미지지 상태 승계" 경고 추가 (D7 셀이 이미 쓰던 패턴 적용) |
| 6 | R1 | Advisory | D5 의 SPI 트리거 기준("built-in 이 부족할 때")이 근거 raw 의 "증명하지 않는 것"과 정확히 일치 | 미조치 (수용) — 노트가 Open Risk 에 자가 flag 중이며 결정 자체는 #KC-BROKER-SPI-C1(L1)이 지지. 운영 전환 시 재평가 |
depth 게이트 2회차 findings (2026-07-17 재감사 — Verdict: Ready, Blocking 0
1회차 Blocking(D8 L0)은 실질 해소 확인 — #KC-HIDELOGIN-C2 의 verbatim 이 zero-change 메커니즘을 직접 진술(L1)하고 #KC-HIDELOGIN-C3 이 경계를 닫음(L2). 2회차 신규/잔여:
| # | 축 | 심각도 | 내용 | 해소 |
|---|---|---|---|---|
| F1 | R1 | Should-fix | 1회차 #2 의 fix 가 반대편으로 좁혀져 재발 — "①표준이 확정: P2B ∈ Browser-based Client" 라벨 자체가 over-claim. raw 의 Usage Boundaries 는 P2B 를 포함한 6조합 전부의 3분류 1:1 대응을 부인 | 해소 — ①을 "표준 확정 = 추상 3패턴의 순서만", ②P2B 매핑(강함·축자 일치), ③P1B 매핑(약함·raw 가 특정 부인)의 3단으로 분리. "P2B < P1B" 는 ②·③ 모두 필요하므로 단정 불가로 명시. D9 착수 판단은 ②만으로 유지됨을 논증 + P2B 매핑 Claims To Verify 1행 추가(P1B 행과 대칭) |
| F2 | R4 | Should-fix | IMPLICIT_DEPENDENCY — 노트 정의가 "P2B = P2A + brokering" 이고 §구현 가이드 §2 의 P2A 열 전량이 P2A 소유인데 P2A 가 의존 목록에 없음(비교 링크뿐, D-ID 없음) |
해소 — §다른 계약 의존 최상단에 P2A D1·D3·D4·D5 행 추가(D5=PKCE baseline, D3=backend 4종 검증 baseline). 부수 발견 2건도 기록: P2A D4 ↔ 본 노트 D8 의 zero-change 이중 주장(/sync), CSRF/state 방어 무소유(draft §6.3.2 MUST + raw 가 "P2 sub-branch 에서 별도 검증"으로 지목 → §TODO) |
| F3 | R2 | Should-fix | D10 추가(1회차 #3 fix)의 부산물 — 소유 목록 2곳(§역할 blurb·§DEM 서두)이 "D5·D8·D9 뿐"으로 남아 D10 이 외래 재진술로 오인·삭제될 위험. 배타적 열거라 단순 오타 이상 |
해소 — 2곳 모두 "D5 · D8 · D9 · D10" 으로 갱신 |
| A1 | R1 | Advisory | D10 의 "Re-auth 자동 폴백" 은 #KC-FBLVERIFY-C3 verbatim("Use this authenticator if the email authenticator is not available")이 관리자 지침문이지 런타임 서술이 아님 — 인용된 어느 claim 도 Re-auth 의 기본 등급을 진술 안 함 |
미조치 (수용) — 인용 출처 절이 "§Default first login flow authenticators" 이고 Claims To Verify 가 "비밀번호 prompt 관찰"로 이미 경험적 포착. 실 스택에서 해소 |
| A2 | R3 | Advisory | 0단계가 비교 임계값이 없어 자력 해제 불가한 게이트 — P2B 배선 전체가 외부 조사 TODO 에 종속 (결함 아닌 일정 리스크) | 미조치 (수용) — 해제 경로(advisory 아카이브 TODO, 우선)가 이미 명시됨 |
마주친 문제
- 아직 없음(문서 단계).
묶음 (자식 sub-sub-branches)
- raw/official-docs/google-oidc-discovery-spec
- raw/official-docs/keycloak-first-broker-login-flow
- raw/official-docs/keycloak-identity-broker-spi
- raw/official-docs/keycloak-identity-provider-mappers
- raw/official-docs/owasp-html5-storage-xss-spa
- raw/branch-notes/feature-keycloak-federation-spa-zero-change
- raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role
- raw/branch-notes/feature-keycloak-three-leg-trust-chain
- raw/branch-notes/feature-keycloak-account-linking-spa-ux
Sources는 상단 "외부 근거" 섹션. Errors/Interview/Lectures/Blog 는 현재 없음.
관련
- root: raw/branch-notes/feature-keycloak-patterns
- P2A (no Google) 비교: raw/branch-notes/feature-keycloak-internal-spa-direct-no-google
- P1B (Edge + Google, brokering 흐름 동일): raw/branch-notes/feature-keycloak-edge-forwardauth-google-federation
- P3B (Single EC2 + Google) 비교: raw/branch-notes/feature-keycloak-single-ec2-google-federation
- (2026-07-17 추가) 결정 owner 브랜치 (본 노트가 consume):
- raw/branch-notes/feature-keycloak-first-broker-login-flow — 사용자 매핑/링크 정책 (D1·D2·D4)
- raw/branch-notes/feature-keycloak-google-claim-attribute-mapping — attribute 매핑·Sync Mode (D3·D4)
- raw/branch-notes/feature-keycloak-idp-brokering-google-client — Google client 등록·scope·trustEmail (D1~D7)
- raw/branch-notes/feature-keycloak-account-linking-sub-vs-email — link key
sub(D1) - raw/branch-notes/feature-keycloak-bff-vs-spa-direct — BFF vs SPA Direct (D1)
- raw/branch-notes/feature-keycloak-spa-token-storage-tradeoff — token 저장 위치 (D1·D2)
관련 일일 노트
완료 후 정리
머지/종료 시점에 채움.
- PR 링크:
- 리뷰 메모:
- 머지 결과 / 배포 환경:
- wiki 추출 대상 (verified만,
wiki/projects/로만 추출):actually-implemented항목:locally-verified항목:prod-verified항목:
- 추출하지 않을 항목 (planned / documented-only / abandoned):
- 본 노트 전량 — 코드 repo 부재로
documented-only/planned(§AuditNO_CODE_REPO)
- 본 노트 전량 — 코드 repo 부재로