514 lines
62 KiB
Markdown
514 lines
62 KiB
Markdown
---
|
|
title: branch / feature-keycloak-internal-spa-direct-google-federation (P2B Internal SPA + Resource Server + Google federation)
|
|
source_type: branch-note
|
|
status: raw
|
|
id: BR-KEYCLOAK-CHILD-028FAA28
|
|
kind: branch-child
|
|
project: keycloak-patterns-overview
|
|
work_item: WI-KEYCLOAK-PATTERNS-OVERVIEW-020
|
|
inherits: [DEC-KEYCLOAK-PATTERNS-OVERVIEW-AUTH-TAXONOMY-001@1]
|
|
refines: []
|
|
overrides: []
|
|
depends_on: []
|
|
contract_packet: 1
|
|
branch: feature-keycloak-internal-spa-direct-google-federation
|
|
parent_branch: feature-keycloak-patterns
|
|
related_projects: [keycloak-patterns]
|
|
tags: [branch, keycloak-patterns, auth, oauth2, oidc]
|
|
created: 2026-05-25
|
|
target_merge:
|
|
status_label: in-progress
|
|
contract_packet_sha256: 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** 이다.
|
|
|
|
<!-- section-id: branch-parent -->
|
|
## 부모 (필수)
|
|
|
|
[[raw/branch-notes/feature-keycloak-patterns]]
|
|
|
|
<!-- GENERATED: branch-contract:start -->
|
|
<!-- section-id: branch-contract-packet -->
|
|
## 브랜치 계약 패킷
|
|
|
|
- **생성 시 프로젝트 개정**: `1`
|
|
- **패킷 스키마**: `contract_packet: 1`
|
|
- **완료 조건**: project governance hub가 AP1~AP4 taxonomy와 child progress index를 유지한다
|
|
|
|
<!-- section-id: inherited-project-decisions -->
|
|
### 상속한 프로젝트 결정
|
|
|
|
| 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]] |
|
|
|
|
<!-- section-id: branch-local-decisions -->
|
|
### 브랜치 지역 결정
|
|
|
|
> 기존 branch-local 결정은 아래 `## Decision Evidence Map / 결정-근거 매핑`의 D-row가 소유하며 이 packet에서 복제하지 않는다.
|
|
|
|
| Decision ID | Decision | Relation | Supporting Claims | Status |
|
|
|---|---|---|---|---|
|
|
|
|
<!-- section-id: declared-overrides -->
|
|
### 선언한 예외
|
|
|
|
| Override ID | Overrides | Reason | Approval | Status |
|
|
|---|---|---|---|---|
|
|
|
|
없음.
|
|
<!-- GENERATED: branch-contract:end -->
|
|
|
|
<!-- section-id: branch-goal -->
|
|
## 목표
|
|
|
|
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:
|
|
|
|
<!-- section-id: branch-scope -->
|
|
## 범위
|
|
|
|
### 포함 범위
|
|
|
|
- **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 (`sub` vs `email`) 및 계정 탈취 시나리오** → [[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` 유지).
|
|
- **`hd` claim → 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가 표준.
|
|
- **비교 핵심**: 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 & Findings `CONTRADICTION-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`
|
|
- [x] **(2026-07-18 정합)** attribute default `IMPORT`와 role mapper override `FORCE`의 적용 계층을 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`
|
|
- [x] **(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.md` Single-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 — 학습 단계에서 `hd` role 제한을 적용하지 않는다. → 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 | Google | 로그인 + consent | **P2B 추가** |
|
|
| **3d** | Google | Browser | 302 → Keycloak `/realms/{r}/broker/google/endpoint?code=...` | **P2B 추가** |
|
|
| **3e** | Keycloak | Google `/token` | code, client_id, client_secret (server-to-server) | **P2B 추가** |
|
|
| **3f** | Google | 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-apps` draft 는 세 패턴을 **보안 감소 순서**로 제시한다 — BFF → Token-Mediating Backend → Browser-based OAuth 2.0 Client (`#OAUTH-BBA-C4`, verbatim: "presented in decreasing order of security").
|
|
>
|
|
> **표준이 확정한 것 vs 본 노트가 매핑한 것을 분리한다** — 아래 ②·③에 ①의 권위를 빌려주면 안 된다:
|
|
>
|
|
> 1. **표준이 확정 (추상 수준)**: 세 패턴의 **보안 감소 순서** 자체 (`#OAUTH-BBA-C4`). draft 는 *추상 3분류*를 정의하고 순서를 매길 뿐, **우리 프로젝트의 P1A~P3B 6조합을 이 분류에 배정해주지 않는다**.
|
|
> 2. **본 노트의 매핑 판정 — 강함**: **P2B ∈ Browser-based OAuth 2.0 Client** (`#OAUTH-BBA-C3`: 브라우저 앱이 public client 로 모든 OAuth 책임을 지고 token 을 직접 보유 → P2B 서술과 축자 일치). 근거는 견고하나 **표준이 확정해준 것은 아니다**.
|
|
> 3. **본 노트의 매핑 판정 — 약함**: **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` / patched `2026-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`+`aud` 4종) = 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 1~3단계 전량 영향.
|
|
- [[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 default `IMPORT`와 role mapper override `FORCE`는 적용 계층이 분리됐다.
|
|
|
|
## 검증해야 할 주장
|
|
|
|
> P2B 는 문서/다이어그램 단계 (`documented-only`). 실 구현 시 검증해야 할 동작:
|
|
|
|
| Claim | Why uncertain | How to verify | Status |
|
|
|---|---|---|---|
|
|
| ~~Keycloak 의 default first-broker-login flow 가 "Automatically Link" 위험 동작을 가짐 (변경 필요)~~ | **(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` |
|
|
| ~~Mapper Sync Mode = FORCE 시 Google 측 name/picture 변경이 다음 로그인 시 overwrite~~ | **(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-spec` pass 의 감사 결과. **자동 수정하지 않고 기록만** 한다 (`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)
|
|
|
|
<!-- GENERATED: sources:start -->
|
|
- [[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]]
|
|
<!-- GENERATED: sources:end -->
|
|
|
|
- [[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` (§Audit `NO_CODE_REPO`)
|