--- title: branch / feature-keycloak-google-claim-attribute-mapping (Google claim → Keycloak attribute mapping) source_type: branch-note status: raw id: BR-KEYCLOAK-PATTERNS-OVERVIEW-017 kind: project-work-item project: keycloak-patterns-overview work_item: WI-KEYCLOAK-PATTERNS-OVERVIEW-017 inherits: [DEC-KEYCLOAK-PATTERNS-OVERVIEW-IDP-BROKERING-001@1] refines: [] overrides: [] depends_on: [WI-KEYCLOAK-PATTERNS-OVERVIEW-015] contract_packet: 1 branch: feature-keycloak-google-claim-attribute-mapping parent_branch: related_projects: [keycloak-patterns] tags: [branch, keycloak-patterns, p1b, identity-provider-mappers, claim-mapping] created: 2026-05-25 target_merge: status_label: in-progress contract_packet_sha256: f1610a6ccdece483c1cdbf85293c304f640dede279cd7bfe7bf4cdb64e1f6e52 --- # branch: feature-keycloak-google-claim-attribute-mapping (Google claim → Keycloak attribute mapping) > Layer: `raw/branch-notes/` — [[raw/project-notes/keycloak-patterns-overview]]의 `WI-KEYCLOAK-PATTERNS-OVERVIEW-017` 직접 branch. > 학습 노트: P1B는 `documented-only` (실 구현 안 함). > `status_label`: `in-progress` > **정합 노트 (2026-07-14 감사)**: 본 노트 = Google claim → Keycloak **attribute-mapping owner** (hub Branch 분해 Tier-2 `feature-keycloak-google-claim-attribute-mapping`). 형제 [[raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role]] 와 매핑 내용이 겹치는데, 그쪽은 **role → 권한(RBAC)** 부분만 남기고 §5 deferred 로 분리됨. > > **정합 노트 (2026-07-16 /branch-spec)**: 근거 승격·delegate·구현 명세 채움 회차. §Decision Evidence Map 에 `선택 조건`(R2) 열 추가, D3 근거 official 승격, D5(`hd`→role) 형제로 delegate, `## 구현 가이드`·`## 엣지·실패·의존` 신설. 자동조사 dispatch 0회(근거 이미 아카이브됨). 상세는 §Audit & Findings. coverage: `governing_docs` 미지정 + `related_projects: [keycloak-patterns]` → **면제**(coverage-gate §7). ## 부모 (필수) [[raw/project-notes/keycloak-patterns-overview]] ## 브랜치 계약 패킷 - **생성 시 프로젝트 개정**: `1` - **패킷 스키마**: `contract_packet: 1` - **완료 조건**: Google email·name claim이 Keycloak attribute로 매핑된다 ### 상속한 프로젝트 결정 | Decision Ref | Project Summary | Branch Application | Source | |---|---|---|---| | `DEC-KEYCLOAK-PATTERNS-OVERVIEW-IDP-BROKERING-001@1` | Google federation은 Keycloak IdP brokering과 hardened First Broker Login을 사용한다 | Google claim을 Keycloak user attribute로 매핑하는 정책에 적용한다 | [[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 | |---|---|---|---|---| 없음. ## 목표 Google ID token에는 `sub`, `email`, `email_verified`, `name`, `given_name`, `family_name`, `picture`, `locale` 등 표준 claim이 포함된다. 이를 Keycloak user의 속성(`firstName`, `lastName`, `email`, custom attribute) 또는 role로 매핑하려면 **Identity Provider Mapper**를 설정해야 한다. 본 노트는 mapper 종류, sync mode, primary key 선택 (sub vs email)의 함의를 정리한다. **핵심 통찰:** - **`sub` claim은 영구·불변** (Google이 사용자별로 발급한 고유 ID). **email은 변경 가능 / 재사용 가능** (자세한 분석은 [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]]). - Keycloak의 federated identity 테이블은 **`identity_provider` + `provider_user_id` (= Google `sub`)**를 primary key로 사용 → email이 바뀌어도 link 유지. - Mapper 모드 (`IMPORT`, `LEGACY`, `FORCE`, `INHERIT`)에 따라 first login 시점에만 매핑 / 매 로그인마다 갱신 / IdP 설정 상속 등으로 동작이 달라짐. ## 범위 > 본 노트 = **attribute-mapping owner** (2026-07-14 감사). claim → *role(RBAC)* 은 형제 소유(아래 delegate). ### 포함 범위 - Google ID token 표준 claim → Keycloak **user attribute** 매핑 (Attribute Importer): `email` / `given_name` / `family_name` / `picture` (D4) - federated identity primary key = Google `sub` (built-in, mapper 불필요) (D1) - Username 생성 정책 (Username Template Importer): `${ALIAS}.${CLAIM.sub}` (D2) - **IdP-level** Sync Mode 선택 (IMPORT vs FORCE) — attribute 최신성 정책 (D3) - mapper 종류 카탈로그 정리 (D6, `needs-confirmation`) ### 제외 범위 > 의도적으로 제외한 것. 대부분 **다른 owner 브랜치로 delegate** — §엣지·실패·의존 의 "다른 계약 의존" 참조. - **claim → role (RBAC 인가) 매핑** (`hd`→role 포함) — owner [[raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role]] (D5 delegate) - **account linking key 안전성** (sub vs email 계정탈취) — owner [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]] - **First Broker Login Flow `email_verified` 게이트 / email auto-linking 방어** — owner [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] - **Client Scope Mapper** (Keycloak user attribute → access token claim, 2단계 전파) — client-level (P2A 계열), 본 IdP-level 범위 밖 - 실제 코드/배포 — 본 P1B sub-sub-branch 전체 `documented-only` / `planned` ## 근거 (필수, 최소 1개+) - [[raw/official-docs/keycloak-identity-provider-mappers]] — Keycloak IdP Mapper 종류 / sync mode 공식 (mapper 종류 `KC-IDP-MAPPER-C4` 는 verbatim 부재 → `needs-confirmation`) - [[raw/official-docs/google-openid-connect-oidc]] — Google ID token 표준 claim (`GOIDC-C3` `sub` 영구·`email` primary key 금지 / `GOIDC-C4` `email` scope) - [[raw/official-docs/google-oidc-discovery-spec]] — D5 (`hd`) 와 D1 (`sub`) 보강: `GOOGLE-OIDC-C7` (`hd` = Google Workspace/Cloud org domain, verbatim) + `GOOGLE-OIDC-C6` (`sub` unique among all Google Accounts and never reused) + `GOOGLE-OIDC-C4` (scope = `openid` + `profile`/`email`). 기존 `related_branches` 에 본 브랜치가 이미 포함돼 있었으나 Sources 표에 미등록이었음 → 2026-07-16 추가 - [[raw/official-docs/keycloak-identity-brokering-overview-official]] — federated identity 모델 - [[raw/official-docs/keycloak-identity-provider-sync-mode-official]] — D3의 IdP-level attribute Sync Mode 공식 근거(IMPORT/FORCE/LEGACY/INHERIT verbatim). 이전 `keycloak-identity-provider-mappers.md#KC-IDP-MAPPER-C5` 의 `needs-confirmation` gap 을 메움 ## TODO 각 항목 옆에 증거 등급. 본 P1B sub-sub-branch는 전체 `documented-only` / `planned`. - [ ] Identity Provider Mapper 종류 정리 — 등급: `documented-only` - **Attribute Importer**: Google claim → Keycloak user attribute (예: `picture` claim → `picture` user attribute) - **Username Template Importer**: Google claim 조합으로 Keycloak username 생성 (예: `${CLAIM.email}` 또는 `${ALIAS}.${CLAIM.sub}`) - **Hardcoded Role**: 본 IdP로 로그인한 모든 사용자에게 특정 role 부여 (예: `realm:user`) - **Hardcoded Attribute**: 모든 broker 사용자에게 같은 속성값 부여 - **Claim to Role**: Google claim 값에 따라 조건부 role 매핑 (예: `hd` claim = `mycompany.com` → `realm:employee`) - **Advanced Attribute to Role / Claim to Group**: 복합 조건 매핑 - [ ] 표준 Google claim 매핑 계획 — 등급: `planned` - `email` → Keycloak `email` (자동 매핑 가능) - `given_name` → Keycloak `firstName` - `family_name` → Keycloak `lastName` - `picture` → Keycloak custom attribute `picture` - `sub` → Keycloak federated identity `provider_user_id` (자동, mapper 불필요) - [ ] Sync Mode 비교 정리 — 등급: `documented-only` - **IMPORT**: first login 시점에만 attribute 복사. 이후 Google 측 변경 무시. - **LEGACY**: deprecated. - **FORCE**: 매 로그인마다 Google claim으로 Keycloak attribute 덮어쓰기. Google 측 변경 자동 반영. - **INHERIT**: IdP 설정의 default sync mode 상속. - [ ] Username 생성 정책 결정 — 등급: `documented-only` - 옵션 A: `email`을 username으로 (가독성 ↑, email 변경 시 username 변경 문제) - 옵션 B: `${ALIAS}.${CLAIM.sub}` (예: `google.1234567890`, 영구 안정 / 가독성 ↓) - 권장: B (불변성 우선) - [ ] `hd` (hosted domain) claim 활용 검토 — 등급: `documented-only` — **DELEGATED → 형제 [[raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role]]** (role/RBAC owner, D5 참조). 본 노트는 `hd` 를 *attribute* 로 import 하는 경우만 §구현 가이드 §1 방식 재사용, *role* 매핑은 형제 소유. - Google Workspace 사용자의 경우 `hd=mycompany.com` claim 제공 (사실 근거: `GOOGLE-OIDC-C7`) - "Claim to Role" mapper로 사내 도메인 사용자에게 자동 role 부여 가능 (→ 형제 D3) ## 진행 중 메모 - Keycloak 25.x 기준 Mapper 설정 UI는 IdP 상세 페이지의 `Mappers` 탭. 새 mapper 추가는 `Add mapper` 버튼. - `Attribute Importer`의 `Claim` 필드는 Google claim 이름 그대로 (예: `email`, `given_name`). 중첩 claim은 dot notation (예: `address.locality`). - `Username Template Importer`는 first login 시점에만 실행 (이후 username 변경 없음) — 따라서 Sync Mode와 무관하게 IMPORT 동작. - email을 username으로 쓰면 사용자 friendly하지만, Google에서 email alias 변경 / 회사 이메일 재배정 시 충돌 발생 가능 → 본 프로젝트는 sub 기반 username 권장. - federated identity 자체는 `sub` 기반 — mapper와 별개로 Keycloak이 internal하게 관리. ## 결정 사항 (decisions) > 본 sub-sub-branch는 `documented-only` — 실 환경 결정 없음. **만약 구현한다면** 권장 기본값. - 2026-05-25: federated identity primary key = Google `sub` claim. email 변경에 robust. - 2026-05-25: Username 생성 = `${ALIAS}.${CLAIM.sub}` (불변성 우선). 사용자에게 보이는 display name은 별도 `name` attribute로 분리. - 2026-05-25: Sync Mode = **IMPORT** (first login 시점 매핑만). 매 로그인마다 덮어쓰기는 사용자 직접 변경한 Keycloak attribute를 매번 되돌려 UX 저하. - 2026-05-25: `email` / `given_name` / `family_name` / `picture` 4개 Attribute Importer 매핑 기본. - 2026-05-25: `hd` claim 기반 role 매핑은 Google Workspace 도입 시점에 추가 (현재는 보류). → **2026-07-16 정합**: 이 `hd`→role 결정은 role/RBAC owner 인 형제 [[raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role]] D3 소유로 **delegate**(본 노트는 attribute-mapping owner). Decision Evidence Map D5 참조. ## 결정-근거 매핑 > 본 sub-sub-branch 는 `documented-only`. cited raw: `keycloak-identity-provider-mappers`, `google-openid-connect-oidc`, `google-oidc-discovery-spec`, `keycloak-identity-provider-sync-mode-official`, `keycloak-identity-brokering-overview-official`. > **2026-07-16 정합 (/branch-spec)** — 상세는 §Audit & Findings: (1) **D3** 근거를 `KC-IDP-MAPPER-C5`(needs-confirmation) → `KC-SYNCMODE-C3/C4/C1`(official-vendor-doc)로 승격 (Sync Mode verbatim 회수됨). (2) **D5**(`hd`→role)를 형제 [[raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role]] D3 으로 **delegate** — 본 노트는 attribute-mapping owner, role/RBAC 는 형제 소유. `hd` 사실 근거는 `GOOGLE-OIDC-C7`(verbatim)로 확보. (3) mapper 종류(`KC-IDP-MAPPER-C4`)는 여전히 `needs-confirmation` — Admin UI 캡처 대상. | Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk | |---|---|---|---|---|---| | D1 | federated identity primary key = Google `sub` claim — email 변경에 robust | N/A — `sub` 는 불변·재사용 없음이라 항상 primary key. `email` 은 어떤 조건에서도 primary key 부적합("shouldn't use email … Always use the sub") | `raw/official-docs/google-openid-connect-oidc.md#GOIDC-C3` ("Always use the `sub` field as it is unique to a Google Account even if the user changes their email address") + `raw/official-docs/google-oidc-discovery-spec.md#GOOGLE-OIDC-C6` (`sub` unique among all Google Accounts and never reused) | `official-vendor-doc` | Keycloak 의 `provider_user_id` 가 정확히 `sub` 로 매핑된다는 verbatim 은 cited raw 에 부재 — Server Admin Guide "Federated Identity" 섹션 raw 추가 필요 (→ Claims To Verify) | | D2 | Username 생성 = `${ALIAS}.${CLAIM.sub}` (불변성 우선); display name 은 별도 `name` attribute 로 분리 | username **안정성(불변)** 우선 시 → sub 기반. **가독성** 우선 + email 재배정/충돌 없음 보장 시 → `${CLAIM.email}` (대안). 학습 노트는 불변성 우선 | `raw/official-docs/google-openid-connect-oidc.md#GOIDC-C3` (email 변경 가능, `sub` 불변) — 배경 정당화. **Username Template Importer 의 `${ALIAS}.${CLAIM.sub}` syntax 자체는** `raw/official-docs/keycloak-identity-provider-mappers.md#KC-IDP-MAPPER-C4` 가 `needs-confirmation` (verbatim 부재) | `official-vendor-doc (sub 불변) + needs-confirmation (mapper syntax)` | Username Template Importer syntax verbatim 확보 필요 (Keycloak 소스 또는 admin UI 캡처) | | D3 | **IdP-level default Sync Mode = `IMPORT`** (profile attribute는 first login 시점 매핑) | 사용자가 Keycloak 에서 **직접 편집한 attribute 를 보존**해야 하면 → IMPORT. Google 측 name/picture 변경을 **매 로그인 자동 반영**해야 하면 → IdP-level FORCE (대안) | `raw/official-docs/keycloak-identity-provider-sync-mode-official.md#KC-SYNCMODE-C3` (`import` = first login 시점 데이터 import, verbatim) + `#KC-SYNCMODE-C4` (`force` = update user data at each user login) + `#KC-SYNCMODE-C1` (IdP-level `Sync Mode` = 모든 mapper default) | `official-vendor-doc` | Keycloak 25.x Admin UI 드롭다운 라벨과 실제 attribute 반영 시점은 realm export/UI 실측 전까지 `needs-confirmation` | | D4 | `email` / `given_name` / `family_name` / `picture` 4개 Attribute Importer 매핑 | N/A — 표준 프로필 claim 을 Keycloak user model 로 옮기는 기본 매핑(선택 분기 없음). scope 는 `openid profile email` 필요 | `raw/official-docs/keycloak-identity-provider-mappers.md#KC-IDP-MAPPER-C1` (incoming token → user/session attribute) + `#KC-IDP-MAPPER-C3` (external credential → user model) + `raw/official-docs/google-oidc-discovery-spec.md#GOOGLE-OIDC-C4` (scope 는 `openid` + `profile`/`email`) | `official-vendor-doc (general mapper + scope) + needs-confirmation (Attribute Importer 화면값)` | Attribute Importer 설정 화면값 verbatim 부재. `given_name`/`family_name`/`picture` 개별 claim 의 Google verbatim 부재 (C4 는 scope 규칙만) | | D5 | `hd` (hosted domain) claim 기반 **role 매핑** — **DELEGATED** (본 노트 결정 범위 밖) | 본 노트(attribute-mapping owner) 범위 밖 — claim → **role(RBAC)** 결정은 형제가 소유. `hd`→role 은 형제 D3 이 결정(Workspace 도입 시 Advanced Claim to Role 추가, 현재 보류) | delegation → [[raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role]] D3. `hd` 사실 근거 = `raw/official-docs/google-oidc-discovery-spec.md#GOOGLE-OIDC-C7` ("The domain associated with the Google Workspace or Cloud organization of the user.") | `delegated (hd 사실근거 official-vendor-doc)` | personal Gmail 의 `hd` 부재 시 mapper 동작(null/skip) — `GOOGLE-OIDC-C7` "does not prove", 형제 노트에서 검증 | | D6 | mapper 종류 5+ 존재 (Attribute Importer, Username Template Importer, Hardcoded Role, Hardcoded Attribute, Claim to Role, Advanced Claim to Role) | N/A — 사실(존재) 진술, 선택 분기 아님 | `raw/official-docs/keycloak-identity-provider-mappers.md#KC-IDP-MAPPER-C4` (구체적 mapper 종류 목록 — strength: `needs-confirmation`, verbatim 부재) | `needs-confirmation` | Keycloak Admin UI 캡처 + Server Admin Guide sub-page 직접 발췌로 mapper 종류 verbatim 확보 | - [[raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role]] D1 — role mapper freshness policy. Role freshness는 본 D3의 IdP-level attribute sync policy와 분리된 별도 owner가 결정한다. ## Audit & Findings (2026-07-16 /branch-spec) > `/branch-spec` 채움 회차의 정합·이관 기록. 결정 본문 아님(추적용). 자동조사 dispatch 0회 — 필요한 근거가 이미 repo 에 아카이브돼 있었음. - **SYNC_MODE_VERBATIM_RECOVERED (D3)**: 근거를 `KC-IDP-MAPPER-C5`(needs-confirmation) → `KC-SYNCMODE-C3/C4/C1`(official-vendor-doc)로 승격. Sync Mode verbatim 을 [[raw/official-docs/keycloak-identity-provider-sync-mode-official]] 이 회수(2026-07-15). 원 소스 `keycloak-identity-provider-mappers.md#KC-IDP-MAPPER-C5` 파일 자체의 strength 승격은 별도 migrate 대상(본 task 밖). - **HD_CLAIM_EVIDENCE_FOUND (D5)**: `hd` 사실 근거가 cited `google-openid-connect-oidc` 엔 없었으나, 이미 repo 에 있던 [[raw/official-docs/google-oidc-discovery-spec]] `#GOOGLE-OIDC-C7`(Workspace domain, verbatim)이 커버 → Sources 표에 추가(그 raw 의 `related_branches` 엔 이미 본 브랜치 포함돼 있었음). D5 는 `UNSUPPORTED_DECISION` 이 아니라 형제로 **delegate**. - **RESTATED_FOREIGN_DECISION (D5)**: `hd`→role 은 형제 [[raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role]] D3 소유(2026-07-14 감사의 attribute/role owner 분리). 본 노트에서 결정으로 재진술하지 않고 delegate 포인터로 전환. - **SYNC_MODE_OWNERSHIP_OVERLAP (2026-07-18 해소)**: 본 노트 D3는 **IdP-level attribute sync policy**만 소유한다. Role freshness 정책은 Decision Evidence Map 아래의 direct-owner pointer로 위임했고, 본 노트에서는 값·메커니즘을 재서술하지 않는다. - **BACKREF_IMPACT (비차단)**: 본 결정 표 수정으로 [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]] · [[raw/branch-notes/feature-keycloak-account-linking-spa-ux]] 의 D1/D3/D4/D5 참조가 영향받을 수 있음. D1/D3/D4 는 의미 불변(evidence 보강만) → 참조 유효. D5 는 의미 변경(deferred → delegated) → 참조처 요약 대조 필요(§Claims To Verify 아래 처리 / `/sync`). ## 구현 가이드 > **본 branch 는 `documented-only`** — 실 Keycloak 등록 전 사전 명세. **IdP-level Mapper**(Identity Provider → Mappers 탭)만 다룬다. user attribute → access token claim 전파(Client Scope Mapper)는 client-level 이라 본 범위 밖(§범위 Out of scope). Keycloak 25.x 기준. ### 1. Attribute Importer mapper 카탈로그 (D4) > **Trace**: D4 — `KC-IDP-MAPPER-C1`(incoming token → user attribute) + `KC-IDP-MAPPER-C3`(external credential → user model) + `GOOGLE-OIDC-C4`(scope = `openid profile email`). Sync Mode 계층은 §3(D3). > > - **UNSUPPORTED_IMPL_DECISION**: (a) mapper instance 명(`google-*`)은 임의 명명 — 근거 raw 에 명명 규칙 없음. trade-off: `google-` prefix 로 provider 출처+대상 claim 을 한눈에. (b) Attribute Importer 의 정확한 UI 필드명(`Claim` / `User Attribute Name`)과 built-in vs custom attribute 구분은 `KC-IDP-MAPPER-C4` `needs-confirmation` — Admin UI 캡처로 확정. | mapper instance (임의명) | mapper type | Google Claim | Keycloak target attribute | 비고 | |---|---|---|---|---| | `google-email` | Attribute Importer | `email` | `email` (built-in user field) | scope `email` 필요(`GOOGLE-OIDC-C4`). email 은 primary key 아님(D1) | | `google-given-name` | Attribute Importer | `given_name` | `firstName` (built-in) | scope `profile` | | `google-family-name` | Attribute Importer | `family_name` | `lastName` (built-in) | scope `profile` | | `google-picture` | Attribute Importer | `picture` | `picture` (custom attribute) | URL 만료 가능(§엣지) | ### 2. Username Template Importer (D2) > **Trace**: D2 — `GOIDC-C3`(sub 불변)이 sub 기반 username 을 정당화. syntax 자체는 `KC-IDP-MAPPER-C4` `needs-confirmation`. > > - **UNSUPPORTED_IMPL_DECISION**: template 문자열 `${ALIAS}.${CLAIM.sub}` 의 정확한 placeholder 문법·구분자는 verbatim 미확보 — Admin UI/소스 확인 필요. trade-off: `${ALIAS}` prefix 로 다중 IdP username 충돌 방지 + `sub` 로 불변성. - Username Template: `${ALIAS}.${CLAIM.sub}` → 예 `google.1234567890` - 실행 시점: **first login 만** (username 은 이후 불변) — Sync Mode 와 무관하게 IMPORT 동작(진행 중 메모). ### 3. IdP-level attribute Sync Mode (D3) > **Trace**: D3 — `KC-SYNCMODE-C1`(IdP-level `Sync Mode` = 모든 mapper default) + `KC-SYNCMODE-C3`(`import` = first login) + `KC-SYNCMODE-C4`(`force` = each login). **official-vendor-doc — UNSUPPORTED 아님.** - **IdP-level `Sync Mode` = `IMPORT`** — §1 의 profile Attribute Importer default 로 상속. - profile attribute를 매 로그인 갱신해야 하는 환경에서는 IdP-level `FORCE`를 대안으로 선택한다(D3). ### 4. federated identity (D1) — built-in, mapper 불필요 > **Trace**: D1 — `GOIDC-C3` + `GOOGLE-OIDC-C6`. Keycloak 이 `(identity_provider, provider_user_id=sub)` 로 internal 관리 → 별도 mapper 없음. > > - **UNSUPPORTED_IMPL_DECISION**: `provider_user_id == sub` 매핑은 Keycloak 내부 동작으로 추정 — verbatim 미확보(Claims To Verify). trade-off: mapper 로 강제하지 않고 built-in 신뢰(공식 문서가 별도 mapper 를 요구하지 않음). ## 엣지·실패·의존 > R4(깊이 게이트) 캡처용. 정상 매핑 경로 외 실패/엣지 + 다른 계약 의존. - **실패·엣지 경로**: - **`email_verified=false` Google 계정**: attribute import 자체는 수행되나 계정 신뢰/링크 안전성은 별개 관심사 — First Broker Login Flow 게이트에 의존(아래). 게이트 없으면 email auto-linking 계정탈취 위험. - **personal Gmail (`hd` 부재)**: `hd` claim 이 없어 `hd` 기반 mapper 는 매칭 안 됨(null/skip 추정). `GOOGLE-OIDC-C7` "does not prove" — 실동작 검증은 형제(D5 delegate). - **`picture` URL 만료**: Google profile picture URL 은 OAuth scope 만료/변경 시 깨질 수 있음 → 장기 저장 시 stale. 표시용으로만 쓰고 캐싱/재조회 정책 별도(needs-confirmation). - **Sync Mode IMPORT 부작용(의도됨)**: Google 측 name/picture 변경이 Keycloak 에 반영 안 됨 → 최신성 필요 시 FORCE 로 전환(D3 대안). - **중첩 claim**: dot notation(`address.locality`) — 진행 중 메모 기준, verbatim 부재(needs-confirmation). - **다른 계약 의존**: - [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]] **D1**(linking key = `sub`) 에 의존 — 본 노트 D1(primary key = sub)이 그 결정을 consume. linking key 가 email 로 바뀌면 본 노트 primary-key 전제 붕괴. - [[raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role]] **D3**(`hd`→role) 에 본 노트 D5 를 delegate — role/RBAC owner. - [[raw/branch-notes/feature-keycloak-idp-brokering-google-client]] **D4**(scope = `openid profile email`) 에 의존 — 본 노트 §구현 가이드 §1 의 Attribute Importer 는 그 브랜치가 IdP client 에 `openid profile email` scope 를 등록해야만 `email`/`given_name`/`family_name`/`picture` claim 이 채워짐(GOOGLE-OIDC-C4). scope 가 축소되면 해당 attribute 가 빈 값. - [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] — `email_verified` 게이트 + email auto-linking 방어 owner (해당 브랜치 결정 번호화 시 그 Decision ID 로 상향 링크). - **Client Scope Mapper**(user attribute → access token claim, 2단계 전파) — client-level(P2A 계열) owner, 본 IdP-level 범위 밖. ## 검증해야 할 주장 | Claim | Why uncertain | How to verify | Status | |---|---|---|---| | Keycloak 의 `federated_identity` 테이블이 `(identity_provider, provider_user_id)` 를 primary key 로 사용하고 `provider_user_id` 가 Google `sub` 와 동일 | Keycloak 내부 스키마의 verbatim 인용 없음 | Keycloak Server Admin Guide "Federated Identity" 섹션 raw 추가 + docker 컨테이너의 H2/Postgres 스키마 직접 확인 | `needs-confirmation` | | Username Template Importer mapper 의 `${ALIAS}.${CLAIM.sub}` syntax 가 실제 동작 | mapper 의 verbatim 인용이 cited raw 에 부재 | Keycloak 25.x docker 실행 후 mapper 등록 + 실제 Google 로그인 → username 생성 결과 확인 | `planned` | | Sync Mode IMPORT 가 first login 시점에만 attribute 매핑, FORCE 는 매 로그인마다 덮어쓰기 | verbatim 은 회수됨(`KC-SYNCMODE-C3`/`C4`, official) — 남은 불확실성은 (a) Keycloak 25.x Admin UI 드롭다운 라벨이 AsciiDoc 원문과 일치하는지 (b) 실제 런타임 반영 시점(token refresh vs full re-login) | Keycloak 25.x 에서 IMPORT vs FORCE 토글 + 2회 로그인 + Google 측 name 변경 시 Keycloak DB 반영 차이 캡처 | `planned` | | Google `hd` claim 이 Workspace 사용자에게만 제공되며 hosted domain 값을 담음 | `hd` = Workspace/Cloud org domain 의 verbatim 은 회수됨(`GOOGLE-OIDC-C7`, official) — 남은 불확실성은 **personal Gmail 의 `hd` 부재 시** mapper 동작(null/skip/거부). C7 "does not prove" 명시. (본 항목은 D5 delegate 대상 — 형제 [[raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role]] 에서 검증) | Workspace 계정 + 개인 Gmail 각각 로그인하여 ID Token 의 `hd` 유무 + Keycloak mapper 반영 차이 확인 | `needs-confirmation` | | Google `given_name` / `family_name` / `picture` claim 이 `profile` scope 요청 시 제공 | cited GOIDC-C4 는 `email` claim 만 다룸 | Google OIDC claims table 의 `profile` scope 섹션 verbatim 발췌 추가 | `needs-confirmation` | | `picture` URL 의 만료/CDN 캐싱 정책 — 장기 저장 시 깨질 수 있음 | Google 측 정책의 verbatim 인용 없음 | Google People API / OIDC `picture` claim 공식 문서 raw 추가 | `needs-confirmation` | ## 마주친 문제 - 아직 없음(문서 단계). ## 묶음 - [[raw/official-docs/google-oidc-discovery-spec]] - [[raw/official-docs/google-openid-connect-oidc]] - [[raw/official-docs/keycloak-google-idp-setup]] - [[raw/official-docs/keycloak-identity-provider-mappers]] - [[raw/official-docs/keycloak-identity-provider-sync-mode-official]] - [[raw/branch-notes/feature-keycloak-google-redirect-uri-policy]] > 본 sub-sub-branch 는 leaf — 자식 자료 없음. Phase 3 P3A 실 구현 또는 외부 산출물 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화. ### 오류 기록 (이 sub-sub-branch 작업 중 발생) - (없음 — 현재 documented-only 단계) ### 면접 준비 (이 작업에서 나올 수 있는 면접 질문) - (없음 — Phase 3 실 구현 단계에 누적) ## 관련 일일 노트 ## 완료 후 정리 - PR 링크: (없음, 문서까지만) - 머지 결과 / 배포 환경: 없음 (`documented-only`) - **wiki 추출 대상:** 없음. P1B sub-sub-branch는 root 정책상 wiki/projects/ 승급 안 함. - **추출하지 않을 항목:** 본 sub-sub-branch 전체 (`documented-only` / `planned`).