Files
llm-wiki/raw/branch-notes/feature-keycloak-edge-forwardauth-google-federation.md
T

379 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: branch / feature-keycloak-edge-forwardauth-google-federation (P1B Edge Forward Auth + Google federation)
source_type: branch-note
status: raw
id: BR-KEYCLOAK-CHILD-8F3B8B4E
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-edge-forwardauth-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: 0d0cfce93fa2e7290518d60b46e5559d479877936fb26cc8dacbf2232cb0d8d3
---
# branch: feature-keycloak-edge-forwardauth-google-federation (P1B Edge Forward Auth + Google federation)
> Layer: `raw/branch-notes/` — root [[raw/branch-notes/feature-keycloak-patterns]]의 sub-branch.
> 패턴 ID: **P1B** — Edge ForwardAuth (oauth2-proxy / Traefik) + Keycloak에 Google을 외부 IdP로 brokering.
> 비교 대상: [[raw/branch-notes/feature-keycloak-edge-forwardauth-no-google]] (P1A: 같은 배치, Google 없음).
> `status_label`: `in-progress`
<!-- section-id: branch-parent -->
## 부모 (필수)
- **Parent branch (root)**: [[raw/branch-notes/feature-keycloak-patterns]]
- **Parent project**: [[raw/project-notes/keycloak-patterns-overview]]
- **Sibling sub-branches**:
- [[raw/branch-notes/feature-keycloak-edge-forwardauth-no-google]] — P1A (Edge, no Google)
- [[raw/branch-notes/feature-keycloak-internal-spa-direct-no-google]] — P2A
- [[raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation]] — P2B
- [[raw/branch-notes/feature-keycloak-single-ec2-no-google]] — P3A
- [[raw/branch-notes/feature-keycloak-single-ec2-google-federation]] — P3B
<!-- 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 변형이다 | Edge ForwardAuth에 Google federation을 결합한 cross-cutting 변형으로 분류한다 | [[raw/project-notes/keycloak-patterns-overview]] |
<!-- section-id: branch-local-decisions -->
### 브랜치 지역 결정
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|---|---|---|---|
| D1 | First Login Review Profile을 기본 off로 둔다 | `local` | [[raw/project-notes/keycloak-patterns-overview]] | `proposed` |
| D2 | automatic email linking 대신 manual confirm을 사용한다 | `local` | [[raw/project-notes/keycloak-patterns-overview]] | `proposed` |
| D3 | environment별 Google OAuth client를 분리한다 | `local` | [[raw/project-notes/keycloak-patterns-overview]] | `proposed` |
| D4 | Google scope를 openid profile email로 제한한다 | `local` | [[raw/project-notes/keycloak-patterns-overview]] | `proposed` |
| D5 | persistent federation key policy는 child owner를 소비한다 | `local` | [[raw/project-notes/keycloak-patterns-overview]] | `proposed` |
<!-- section-id: declared-overrides -->
### 선언한 예외
| Override ID | Overrides | Reason | Approval | Status |
|---|---|---|---|---|
없음.
<!-- GENERATED: branch-contract:end -->
## 묶음 (자식 sub-sub-branches)
<!-- GENERATED: sources:start -->
- [[raw/company-tech-blogs/keycloak-google-login-codemancers]]
- [[raw/official-docs/google-openid-connect-oidc]]
- [[raw/official-docs/keycloak-first-login-flow]]
- [[raw/official-docs/keycloak-google-idp-setup]]
<!-- GENERATED: sources:end -->
- [[raw/branch-notes/feature-keycloak-idp-brokering-google-client]]
- [[raw/branch-notes/feature-keycloak-first-broker-login-flow]]
- [[raw/branch-notes/feature-keycloak-google-claim-attribute-mapping]]
- [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]]
> Sources는 하단 "외부 근거" 섹션. Errors/Interview/Lectures/Blog 는 현재 없음.
<!-- section-id: branch-goal -->
## 목표
P1A에 외부 IdP(Google)가 붙으면 토큰 흐름이 어떻게 확장되는지를 명확히 한다.
**핵심 통찰:** **edge proxy 입장에서는 변화가 없다.** oauth2-proxy는 여전히 Keycloak 한 곳에만 redirect 하고, Keycloak이 발급한 Keycloak access token만 받는다. Google federation은 **Keycloak 내부에서 일어나는 외부 IdP brokering 흐름**이며, edge / backend 입장에서는 투명(transparent)하다.
면접 / 설계 시 자주 헷갈리는 지점:
- "Google 로그인을 붙이면 backend가 Google ID token을 검증해야 하나?" → **아니다.** P1B backend trust는 [[raw/branch-notes/feature-keycloak-edge-forwardauth-no-google]] D3 — P1A와 같은 header-only 계약이며 Google token과 Keycloak token을 backend로 전달하지 않는다.
- "edge proxy가 Google client secret을 알아야 하나?" → **아니다.** Google credential은 Keycloak이 보관·사용.
- "추가되는 trust hop은 어디인가?" → **Google → Keycloak.** P1A 대비 추가된 신뢰 경계는 이 한 hop.
<!-- section-id: branch-scope -->
## 범위
### 포함 범위
- P1B 컴포넌트 다이어그램 (P1A 대비 추가 컴포넌트 표시)
- P1A 대비 추가되는 토큰 교환 단계 (4–8) 명시
- Google federation 시 추가되는 신뢰 경계와 보안 surface
- First Login Flow 정책 결정 지점 (Review Profile / Account Linking)
- P1A 대비 장단점 / 운영 비용 비교
### 제외 범위
- 실제 구현 (P1B는 문서까지만 — root의 implementation 대상은 P3A)
- Google 외 IdP (GitHub / Facebook / Apple). Google만.
- Keycloak Authentication Flow custom code (Java SPI). 설정 옵션 수준까지.
- prod 환경 Google API rate limit / quota 분석.
## 근거 (필수, 최소 1개+)
> 본 sub-branch의 P1B (Edge + Google federation) 채택 근거. 상세 대안 비교는 §외부 근거 / 대안 조사 참조.
| Source | 정당화하는 결정 |
|---|---|
| [[raw/official-docs/keycloak-identity-brokering-overview-official]] | Keycloak Identity Broker 공식 — Google IdP brokering 패턴 채택 근거 |
| [[raw/official-docs/keycloak-google-idp-setup]] | Keycloak Google IdP setup 절차 — 구성 단계 근거 |
| [[raw/official-docs/google-openid-connect-oidc]] | Google OIDC 표준 (issuer, scopes, claims) — Google IdP 표준 동작 근거 |
| [[raw/official-docs/keycloak-first-login-flow]] | First Broker Login Flow — Account Linking 정책 근거 |
| [[raw/company-tech-blogs/keycloak-google-login-codemancers]] | Keycloak + Google 통합 실무 사례 (참고) |
## 컴포넌트 다이어그램
```
Browser
Ingress (nginx / Traefik)
ForwardAuth (oauth2-proxy) ────► Keycloak (realm: app)
│ (사용자가 "Sign in with Google" 클릭)
Google OIDC
(authorize / token / userinfo)
│ (Google ID token + access token)
Keycloak
(First Login Flow:
Google sub/email → Keycloak user
매핑 또는 신규 생성)
│ (Keycloak access token 발급)
oauth2-proxy
│ (proxy 세션 cookie 셋팅 + 헤더 주입)
Ingress
Backend
- edge가 주입한 trusted header만 신뢰
- JWT/Google token은 보지 않음
```
핵심 표시:
- **edge proxy ↔ Keycloak 구간 = P1A와 동일.**
- **Keycloak ↔ Google 구간 = P1B에서 새로 추가된 leg.**
- **backend trust = [[raw/branch-notes/feature-keycloak-edge-forwardauth-no-google]] D3 — edge-injected header only.** Google federation은 이 경계를 바꾸지 않는다.
## 토큰 교환 sequence (P1A 대비 추가 단계 포함)
P1A 단계와 일치하는 부분은 그대로, Google federation 분기만 새 번호로 표기.
1. (P1A 1과 동일) 사용자 브라우저가 보호 리소스 GET → Ingress → oauth2-proxy.
2. (P1A 2와 동일) oauth2-proxy: 세션 없음 → Keycloak `authorize` redirect.
3. (P1A 3과 동일) Keycloak 로그인 페이지 표시.
4. **(추가)** Keycloak 로그인 UI에 "Sign in with Google" 버튼 노출 (Identity Provider로 Google 등록 시 자동).
5. **(추가)** 사용자 버튼 클릭 → Keycloak → Google `authorize` endpoint redirect (`https://accounts.google.com/o/oauth2/v2/auth`, scope=`openid profile email`).
6. **(추가)** 사용자 Google 로그인 → Google → Keycloak broker callback (`/realms/<realm>/broker/google/endpoint`, `code` 전달).
7. **(추가)** Keycloak → Google `/token` (`https://oauth2.googleapis.com/token`), Google ID token + access token 수신.
8. **(추가)** Keycloak: Google ID token의 `sub`(영구 식별자) / `email` claim → **First Login Flow** 진입.
- 기존 federated user 있음 → 그대로 매핑된 Keycloak user 사용.
- 없고 email match로 기존 local user 있음 → Handle Existing Account 서브플로우 (자동 링크 / 수동 confirm).
- 둘 다 없음 → 신규 Keycloak user 생성 (Review Profile 옵션에 따라 확인 페이지).
9. (P1A 5와 동일) Keycloak → oauth2-proxy callback (`/oauth2/callback`, `code` 전달). oauth2-proxy → Keycloak `/token`. **Keycloak access token + refresh token + ID token 발급.**
10. (P1A 67과 동일) oauth2-proxy: 세션 cookie 셋팅 + 헤더(`X-Auth-Request-Email` 등) 주입 후 backend로 forward. P1B 기본 계약은 bearer token을 전달하지 않고 backend가 edge header만 신뢰한다.
## 신뢰 경계 (P1A 대비 변화)
| 경계 | P1A | P1B |
|------|-----|-----|
| Browser ↔ oauth2-proxy | TLS, 세션 cookie | 동일 |
| oauth2-proxy ↔ Keycloak | TLS, client secret | 동일 |
| Keycloak ↔ Google | — | **신규.** TLS, Google OAuth client secret (Keycloak이 보관) |
| oauth2-proxy ↔ Backend | trusted identity header (bearer token 미전달) | 동일 |
| Backend의 token 검증 | 없음 — edge에서 인증 종결, header-only | 동일 — Google federation만 추가 |
신규 trust hop = **Google → Keycloak 한 개.** Google ID token signature는 Keycloak이 검증하고 oauth2-proxy는 Keycloak 세션을 만든다. 그 이후 backend trust는 P1A와 같은 header-only 경계다.
## 장점 / 단점 vs P1A
### 장점
- 사용자가 **Google 계정으로 로그인 가능** → 별도 비밀번호 관리 불필요. UX 개선.
- 조직이 Google Workspace 사용 중이면 사실상의 SSO 통합 (사내 Google 계정 그대로 사용).
- Keycloak이 brokering 하므로 **edge/backend 코드 변화 0** — P1A에서 Identity Provider만 추가 설정.
- 다른 외부 IdP(Microsoft / GitHub) 추가 시에도 동일 패턴으로 확장 가능 (broker만 추가 등록).
### 단점
- **외부 의존:** Google OIDC downtime / rate limit 시 신규 로그인 불가 (이미 발급된 Keycloak 세션은 영향 없음).
- **사용자 매핑 정책 운영 부담:** First Login Flow / Account Linking 정책 결정 필요. 잘못 설정 시 보안 이슈 (자동 email match linking → account takeover 위험).
- **보안 surface 확장:**
- Google OAuth client secret이 Keycloak DB(또는 vault)에 저장됨.
- Google Cloud Console의 redirect URI 등록 관리 (환경별 OAuth client 분리 필요).
- Google 측 권한 변경(예: scope 변경, OAuth verification 요구) 시 영향 받음.
- **개인정보 / 동의 흐름 추가:** Google scope 동의 화면, GDPR 등 데이터 처리 정책 영향.
- **디버깅 복잡도:** 로그인 실패 시 oauth2-proxy / Keycloak / Google 3-leg 중 어디서 실패했는지 추적 필요 (로그 corr id 설계 중요).
## 결정 사항 (decisions)
본 sub-branch는 문서까지만 (`documented-only`)이므로 실제 환경 결정은 없음. **만약 구현한다면** 권장 기본값:
- **D1 — First Login Flow / Review Profile:** OFF (Google이 email/profile 제공하므로 불필요). 단, 신규 사용자 동의 페이지가 필요한 비즈니스 요건이면 ON.
- **D2 — Account Linking:** **수동 confirm.** 공식 문서가 "automatic linking by email = potential security hole" 명시. email match 시 사용자가 명시적으로 link 확인하도록.
- **D3 — Google OAuth client 분리:** dev / staging / prod 환경별 별도 OAuth client. redirect URI 충돌 방지.
- **D4 — scope:** `openid profile email`만. (추정 — 추가 scope 요청 시 Google OAuth verification 이 트리거될 수 있으나 인용 raw 가 enumerate 안 함, 별도 raw 확보 전까지 근거 미보증.)
- **D5 — persistent federation key requirement:** [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]] D1 — concrete key policy owner. 본 parent는 안정적인 외부 subject를 사용한다는 P1B invariant만 consume한다.
`needs-confirmation`:
- Keycloak 세션 만료 시 Google refresh token으로 자동 갱신 가능 여부 (Keycloak이 Google refresh token을 보관하나? 정책상 사용자 재로그인이 일반적).
- Google account 삭제 / suspend 시 Keycloak local user 자동 비활성화 여부.
## 결정-근거 매핑
> P1B 권장 기본값 결정과 raw source claim 매핑. company-tech-blog (`codemancers`) claim 은 보조 근거 — 공식 best practice 로 격상 금지 (CLAUDE.md §5).
> `선택 조건` 열(R2): 각 결정이 "어떤 조건일 때 이 값, 다른 조건이면 어떤 대안" 인지. 상세 근거는 §결정 사항 prose.
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|---|
| D1 | First Login Flow / Review Profile = OFF (Google이 email/profile 제공하므로 불필요) | Google 이 `email`+`profile` 을 제공 → `Off`. 신규 사용자 동의/추가 attribute 수집이 비즈니스 요건이면 `On`, mandatory attr 부재 대비 fallback 만이면 `missing` (KC-FLF-C4 의 3-mode) | `raw/official-docs/keycloak-first-login-flow.md#KC-FLF-C4`, `raw/official-docs/keycloak-google-idp-setup.md#KC-GIDP-C5`, `raw/official-docs/google-openid-connect-oidc.md#GOIDC-C4` | `official-vendor-doc (Keycloak) + official-standard (Google OIDC)` | KC-FLF-C4 의 "mandatory information" 정확한 목록 (locale 포함 여부 등) 미확정 — `Off` mode 에서도 누락 시 자동 fallback 동작 확인 필요 |
| D2 | Account Linking = 수동 confirm (자동 email link 금지) | 외부 IdP email 을 항상 신뢰할 수 없음이 기본(KC-FLF-C2 공식 경고) → 항상 Confirm Link. 자동 email link 는 통제된 신뢰 환경에서도 공식 경고 대상이라 채택 안 함 (대안 없음) | `raw/official-docs/keycloak-first-login-flow.md#KC-FLF-C2`, `raw/official-docs/keycloak-first-login-flow.md#KC-FLF-C3` | `official-vendor-doc` (Keycloak 공식 security warning 명시) | "Confirm Link Existing Account" 가 Keycloak version 별 default flow 에 포함되는지 vs 별도 추가인지 — KC-FLF-C3 의 "Does not prove" 에 명시 — version 별 확인 필요 |
| D3 | dev / staging / prod 환경별 별도 Google OAuth client | 환경별 redirect URI/도메인이 다름 → 환경당 별도 OAuth client. 단일 도메인·단일 환경이면 client 1개 + 다중 redirect URI 로도 가능 | `raw/official-docs/keycloak-google-idp-setup.md#KC-GIDP-C3`, `raw/official-docs/keycloak-google-idp-setup.md#KC-GIDP-C4`, `raw/company-tech-blogs/keycloak-google-login-codemancers.md#CM-KC-GG-C4` (보조 — company case study, 공식 best practice 아님) | `official-vendor-doc (Keycloak) + company-case-study (codemancers, 보조)` | "환경별 분리가 redirect URI 충돌 방지에 필수" 는 일반 운영 원칙으로 raw claim 들이 직접 명시하지 않음 — KC-GIDP-C4 의 wildcard / 부분 매칭 허용 여부가 raw 범위 밖 |
| D4 | scope = `openid profile email` 만 | `email`/`profile` 매핑만 필요 → Keycloak default 3 scope 유지. `hd`(도메인 제한)·groups 등 추가 사용자 데이터가 필요할 때만 scope 추가 | `raw/official-docs/keycloak-google-idp-setup.md#KC-GIDP-C5`, `raw/official-docs/google-openid-connect-oidc.md#GOIDC-C4` | `official-vendor-doc (Keycloak default) + official-standard (Google OIDC scope contract)` | "추가 scope 요청 시 Google OAuth verification 트리거" 는 본 raw 들이 직접 enumerate 하지 않음 — Google OAuth verification 정책 별도 raw 보존 필요 |
| D5 | P1B는 stable external subject를 요구하며 concrete persistent federation key policy를 직접 소유하지 않음 | key 선택·변경은 child owner에서만 수행. parent는 그 결과를 P1B topology invariant로 consume | [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]] D1 — persistent Google federation key owner | `delegated` | Keycloak 저장 메커니즘과 initial collision locator의 차이는 child에서 추적; parent에 복제하지 않음 |
## 구현 가이드
> **본 브랜치는 `documented-only` 설계 hub** — P1B topology와 pattern-level 요구만 소유하고 Google brokering의 mutable 설정은 자식 브랜치(§Cluster 4개)에 위임한다. 아래 parent D-row는 자식 owner의 값을 복제하지 않고 pointer로 consume한다. **모든 항목 등급 `planned`** — 구현 repo(`keycloak-patterns/`)가 아직 없어 코드로 확정된 것은 0개(`NO_GROUND_TRUTH`, ground truth = 공식 raw 문서). 코드 확인 후 등급 승격.
>
> **3-rule 준수**: 각 sub-section 은 Decision ID + Supporting Claim ID 를 Trace. 근거 raw 가 *원칙* 만 주고 *detail* 은 안 주는 cell 은 `UNSUPPORTED_IMPL_DECISION` + trade-off 한 줄. 본 브랜치 결정 범위 밖 detail(Java SPI custom code 등)은 §범위 out-of-scope 로 이미 배제.
### 1. Google IdP 등록 (realm config) — D3 · D4 + delegated D5
> **Trace**: D3(환경별 client)·D4(scope). [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]] D1 — persistent federation key owner. IdP client 설정 owner는 [[raw/branch-notes/feature-keycloak-idp-brokering-google-client]].
>
> - **UNSUPPORTED_IMPL_DECISION**: (a) redirect URI 정확한 path 형식 — raw 가 명시 안 함(KC-GIDP-C3 "does not prove"), Admin Console 자동표시값을 복사(Claim To Verify #6 가 추적). trade-off: path 를 하드코딩하면 `KC_HTTP_RELATIVE_PATH`/host 조합 변화에 취약 → UI 표시값 복사가 안전. (b) discovery endpoint cache/retry 동작 — GOIDC-C5 는 URL 만 줌(Claim To Verify #1). (c) 환경별 client 분리 — official 미보증 운영 추론(KC-GIDP-C3/C4 는 양방향 등록까지만). trade-off: 단일 client 다중 redirect URI 도 가능하나 환경 간 실수 유출 위험 → 환경별 분리가 안전측.
| 설정 항목 | 값 / 경로 | Trace | 등급 |
|---|---|---|---|
| IdP 등록 진입 | Admin Console → `Identity Providers``Add provider``Google` | KC-GIDP-C1 | `planned` |
| Client 자격 | Google 발급 `Client ID` + `Client Secret` 을 Keycloak Google IdP 에 입력 (Keycloak DB/vault 보관) | KC-GIDP-C2, D3 | `planned` |
| 환경 분리 | dev/staging/prod 각 환경별 Google OAuth client 별도 발급 (redirect URI 충돌 방지). **UNSUPPORTED_IMPL_DECISION** — official 미보증 운영 추론 | D3(운영 추론, KC-GIDP-C3/C4 는 양방향 등록까지만 L1 증명) · CM-KC-GG-C4(보조) | `planned` |
| Redirect URI | Keycloak `Add Identity Provider` 페이지 표시값 → Google Cloud Console `Authorized redirect URIs` 에 복사. 예상 형식 `https://<keycloak-host>/realms/<realm>/broker/google/endpoint` (**UNSUPPORTED_IMPL_DECISION** — 형식은 raw 인용 밖, UI 표시값 신뢰) | KC-GIDP-C3, KC-GIDP-C4 | `planned` |
| Scope | Default Scopes = `openid profile email` 유지, 추가 scope 금지(D4 조건) | KC-GIDP-C5, D4 | `planned` |
| Discovery | "Import from URL" = `https://accounts.google.com/.well-known/openid-configuration` | GOIDC-C5 | `planned` (Claim To Verify #1) |
### 2. First Broker Login Flow hardening — D1 · D2
> **Trace**: D1(Review Profile)·D2(Confirm Link) / Claims `KC-FLF-C2`, `KC-FLF-C3`, `KC-FLF-C4`. Owner 자식: [[raw/branch-notes/feature-keycloak-first-broker-login-flow]].
>
> - **OUT_OF_BRANCH_SCOPE**: flow 구성의 정본은 [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D1 — silent auto-link 방지, [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D2 — verification profile, [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D4 — custom hard-reject SPI 없이 성립하는 core policy. SPI artifact가 없는 현재 scope에서 `email_verified=false` 전체 hard-reject를 parent acceptance로 주장하지 않는다.
| Flow 설정 | 값 | Trace | 등급 |
|---|---|---|---|
| Flow 편집 전 복제 | built-in "first broker login" flow 를 복제 후 수정 (원본 훼손 시 외부 IdP 전체 차단 위험) | KC-FLF 운영맥락(L90) | `planned` |
| Review Profile authenticator | `Off` (Google 이 email+profile 제공). 동의 페이지 필요 시만 `On`, mandatory 부재 대비만이면 `missing` | KC-FLF-C4, D1 | `planned` |
| Confirm Link Existing Account | required — 자동 email link 금지, 사용자 명시 confirm 강제 (info page: 다른 email 사용 vs link 확인) | KC-FLF-C2, KC-FLF-C3, D2 | `planned` |
| linking trust policy | [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D4 — silent auto-link 방지 core를 consume. `email_verified=false` 전체 hard-reject는 custom SPI가 별도 채택될 때만 추가 | delegated | `documented-only`; SPI variant는 out-of-scope |
### 3. Account matching identifier (IdP mapper) — D5
> **Trace**: D5. [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]] D1 — persistent federation key owner. [[raw/branch-notes/feature-keycloak-google-claim-attribute-mapping]] D4 — claim→attribute mapper owner.
>
> - **UNSUPPORTED_IMPL_DECISION**: `sub` 를 federated identity primary key 로 매핑하는 정확한 Keycloak IdP mapper 타입/옵션 — 참조 raw 미포함(D5 Open Risk 가 `keycloak-identity-provider-mappers.md` 필요 명시). trade-off: mapper 타입 임의 선택 시 재로그인 매칭 실패 가능 → mapper raw 확보 후 자식 브랜치에서 확정.
- Persistent federation key와 mapper 타입/옵션은 위 두 owner D-row를 참조한다. 본 parent는 stable external subject requirement만 유지한다.
## 엣지·실패·의존
> R4 캡처용. 본 브랜치는 `documented-only` 지만, Google leg 추가로 P1A 대비 새 실패 경로가 생기고, 자식·형제 브랜치와 계약 의존이 있다.
- **실패·엣지 경로**:
- **Google OIDC downtime / rate limit**: 신규 로그인 불가. 이미 발급된 Keycloak 세션은 영향 없음 (Google leg 는 최초 인증 시에만) — §장점/단점 근거.
- **email 기반 auto-linking 계정 탈취**: opt-in AutoLink가 email 값을 무확인 연결하면 기존 계정 탈취 가능. 방어 정본은 [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D1과 [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D4이며, 이 parent는 `email_verified=true` hard-reject를 별도 acceptance로 두지 않는다.
- **redirect URI mismatch**: Google Cloud Console `Authorized redirect URIs` ↔ Keycloak `/broker/google/endpoint` 불일치 시 Google 측 오류. 환경별 client 분리(D3)로 완화 (Claim To Verify #6).
- **bearer token 경계 누출**: P1B baseline backend에는 access token 자체가 전달되지 않는다. `Authorization` header가 관측되거나 Google token이 backend까지 새면 [[raw/branch-notes/feature-keycloak-edge-forwardauth-no-google]] D3의 header-only trust 경계가 붕괴한 것이다(Claim To Verify #5).
- **3-leg 디버깅 복잡도**: 로그인 실패 시 oauth2-proxy / Keycloak / Google 중 실패 지점 추적 필요 → corr-id 설계 (§장점/단점).
- **세션·계정 수명 불일치** (`needs-confirmation`): Keycloak 세션 만료 시 Google refresh 자동 갱신 여부(Claim To Verify #2), Google 계정 삭제/suspend 시 Keycloak local user 미비활성(Claim To Verify #3).
- **다른 계약 의존**:
- **Root**: [[raw/branch-notes/feature-keycloak-patterns]] 의 고정 결정 F3(단일 공유 realm + 패턴당 client)·F4(confidential secret = env var, 미커밋)에 의존 — Google client secret 저장 정책은 F4 를 따름.
- **자식(위임 — detail owner)**: [[raw/branch-notes/feature-keycloak-idp-brokering-google-client]] D3 — client 등록. [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D1 — silent auto-link 방지, [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D2 — verification profile, [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D4 — core linking policy. [[raw/branch-notes/feature-keycloak-google-claim-attribute-mapping]] D4 — claim mapping. [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]] D1 — persistent federation key. Parent는 topology와 요구만 소유한다.
- **형제(브로커 로직 재사용)**: [[raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation]](P2B), [[raw/branch-notes/feature-keycloak-single-ec2-google-federation]](P3B) — 동일 Google brokering 흐름. P3B 실 구현 시 `KC_HOSTNAME` 공개 host 강제(Google redirect_uri 검증)라는 배포측 추가 의존.
## 검증해야 할 주장
> 본 sub-branch 는 `documented-only`. 만약 구현한다면 검증해야 할 주장 enumerate.
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
| Keycloak 의 Google IdP "Use discovery endpoint" 옵션이 `https://accounts.google.com/.well-known/openid-configuration` 으로 정상 OIDC discovery 수행 | `GOIDC-C5` 가 URL 명시했으나, Keycloak 의 discovery 호출 실제 동작 (cache TTL, retry 정책 등) 별도 검증 | Keycloak Admin Console > Identity Provider > Google > "Import from URL" 클릭 후 endpoint 자동 채워지는지 확인 + keycloak debug log 에서 GET 요청 확인 | `planned` |
| Keycloak 세션 만료 시 Google refresh token 으로 자동 갱신 가능 여부 | 본 sub-branch §결정 사항 `needs-confirmation` 명시 — Keycloak 이 Google refresh token 을 보관하는지 정책 불명 | Keycloak Admin Console > Identity Provider > Google > "Store Tokens" 옵션 활성화 후 세션 만료 후 동작 관찰 + Keycloak DB `federated_identity` 테이블 검사 | `needs-confirmation` |
| Google account 삭제 / suspend 시 Keycloak local user 자동 비활성화 여부 | 본 sub-branch §결정 사항 `needs-confirmation` 명시 — Keycloak 에 webhook / polling 메커니즘 없음 (일반적) | Google Cloud Console 에서 test account suspend 후 Keycloak login 시도 → 거부 여부 관찰 (예상: Keycloak 은 모름, login 시점에 Google 측 401 로만 차단) | `needs-confirmation` |
| First Broker Login Flow 의 "Confirm Link Existing Account" authenticator 가 Keycloak 26.x default flow 에 포함됨 | `KC-FLF-C3` 가 authenticator 존재 명시했으나 version 별 default flow 포함 여부 별도 확인 필요 | Keycloak Admin Console > Authentication > Flows > "first broker login" flow 확인 + "Confirm Link Existing Account" step 존재 여부 | `planned` |
| P1B가 P1A의 header-only backend trust를 유지하고 Google/Keycloak bearer token을 backend로 전달하지 않음 | [[raw/branch-notes/feature-keycloak-edge-forwardauth-no-google]] D3을 consume하지만 실제 proxy config는 아직 없음 | Google 로그인 후 oauth2-proxy/ingress config와 backend request capture에서 trusted headers 존재, `Authorization` 부재, direct spoof 요청 차단을 확인 | `planned` |
| Google OAuth client 의 redirect URI 가 `/realms/<realm>/broker/google/endpoint` 형식과 정확히 일치 | `KC-IDP-BROKER-C2` 가 verbatim 부재 명시 — Admin UI 표시값을 신뢰 | Keycloak Admin Console > Identity Provider > Google 페이지의 "Redirect URI" 필드 값을 복사 → Google Cloud Console 의 Authorized redirect URIs 와 byte-level 일치 확인 | `planned` |
| (Deferred SPI variant) `email_verified=false`를 flow 진입 즉시 hard-reject하는 custom authenticator를 별도 구현할 필요가 있는가 | 현재 corpus에 provider JAR/SPI artifact가 없고 core silent-link 방지는 [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D4로 성립 | 별도 제품 요구가 생길 때 SPI branch를 만들고 provider JAR, flow export, false-email negative E2E를 함께 검증 | `planned` (현재 acceptance 아님) |
## 외부 근거 / 대안 조사 (2026-05-25 — P1B Edge + Google IdP Brokering)
본 sub-branch의 **Edge ForwardAuth + Google IdP Brokering** 채택에 대한 외부 source. P1A에 외부 IdP federation을 추가하는 방식의 대안 비교.
- **채택 결정 (Keycloak IdP Brokering — Google을 외부 IdP로 등록)**:
- [[raw/official-docs/keycloak-identity-brokering-overview-official]] — Keycloak Identity Broker 공식 (외부 IdP 등록 + first broker login flow)
- [[raw/official-docs/keycloak-google-idp-setup]] — Keycloak Google IdP setup 절차
- [[raw/official-docs/google-openid-connect-oidc]] — Google OIDC 표준 (issuer, scopes, claims)
- [[raw/official-docs/keycloak-first-login-flow]] — First Broker Login Flow (Account Linking 정책)
- [[raw/company-tech-blogs/keycloak-google-login-codemancers]] — Keycloak + Google 통합 실무 사례
- **검토한 대안**:
- **대안 1: SAML federation (Keycloak ↔ Google Workspace SAML)** — 엔터프라이즈 단일 사인온 표준. 그러나 Google OIDC가 더 단순.
- **대안 2: Google OIDC 직접 (Keycloak 우회)** — 백엔드가 Google ID token 직접 검증. 장: Keycloak 운영 부담 0 / 단: 다중 IdP 통합 어려움, role mapping 직접 작성.
- **대안 3: Auth0 / Okta (managed multi-IdP SaaS)** — 운영 완전 위임. 단: vendor lock-in, 비용.
- **대안 4: oauth2-proxy `--provider=google` 직접** — Keycloak 없이 oauth2-proxy가 Google과 직접 통신. 장: Keycloak 제거 / 단: realm/role 관리 불가, 멀티 IdP 통합 불가.
- **대안 5: AWS Cognito + Google federation** — AWS 종속, 동일 패턴.
- **비교 핵심**: IdP Brokering의 **본질적 가치는 "코드 변경 없이 IdP 추가"**. SPA/백엔드는 Keycloak만 알면 되고, Google/SAML/LDAP 추가는 Keycloak admin 설정만. AutoLink는 별도 opt-in 위험 기능이며, 본 패턴은 [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D1/D4의 silent-link 방지 core를 따른다.
## TODO
각 항목 옆에 증거 등급.
- [ ] P1B 다이어그램을 Mermaid sequence로 재작성 — 등급: `planned`
- [ ] P1A vs P1B diff matrix (sequence 단계 / trust boundary / 운영 비용) — 등급: `planned`
- [ ] First Login Flow 정책 분기 트리 도식화 — 등급: `documented-only` (구현 안 함)
- [ ] Keycloak refresh / Google session 만료 상호작용 확인 — 등급: `needs-confirmation`
## 진행 중 메모
- Google ID token은 Keycloak이 검증한 뒤 backend로 전달하지 않는다. oauth2-proxy가 Keycloak token으로 session을 만들고, P1B backend는 [[raw/branch-notes/feature-keycloak-edge-forwardauth-no-google]] D3과 같은 **edge-injected header-only** 계약만 소비한다. 따라서 backend로 Google/Keycloak bearer token이 흐른다고 표현하지 않는다.
- "Google 로그인 = backend가 Google과 통신"으로 오해하기 쉬움. 다이어그램에서 Google ↔ Keycloak leg를 별도 색/박스로 강조해야 함.
- 본 sub-branch는 `documented-only` 한정 — wiki/projects/로 승급 안 함 (root TODO 참조).
## 마주친 문제
- 없음 (구현 안 함).
## 관련 sub-branch
- [[raw/branch-notes/feature-keycloak-patterns]] (root)
- [[raw/branch-notes/feature-keycloak-edge-forwardauth-no-google]] (P1A — Google 없는 동일 배치, 비교 기준)
- [[raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation]] (P2B — 내부 배치 + Google, broker 로직은 본 노트와 동일)
- [[raw/branch-notes/feature-keycloak-single-ec2-google-federation]] (P3B — 단일 EC2 + Google)
## 관련 일일 노트
## 완료 후 정리
- PR 링크: (없음, 문서까지만)
- 머지 결과 / 배포 환경: 없음 (`documented-only`)
- **wiki 추출 대상:** 없음. P1B는 root 정책상 wiki/projects/ 승급 안 함.
- **추출하지 않을 항목:** 본 sub-branch 전체 (`documented-only` / `planned`).