292 lines
27 KiB
Markdown
292 lines
27 KiB
Markdown
---
|
|
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).
|
|
|
|
<!-- section-id: branch-parent -->
|
|
## 부모 (필수)
|
|
|
|
[[raw/project-notes/keycloak-patterns-overview]]
|
|
|
|
<!-- GENERATED: branch-contract:start -->
|
|
<!-- section-id: branch-contract-packet -->
|
|
## 브랜치 계약 패킷
|
|
|
|
- **생성 시 프로젝트 개정**: `1`
|
|
- **패킷 스키마**: `contract_packet: 1`
|
|
- **완료 조건**: Google email·name claim이 Keycloak attribute로 매핑된다
|
|
|
|
<!-- section-id: inherited-project-decisions -->
|
|
### 상속한 프로젝트 결정
|
|
|
|
| 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]] |
|
|
|
|
<!-- 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 -->
|
|
## 목표
|
|
|
|
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 설정 상속 등으로 동작이 달라짐.
|
|
|
|
<!-- section-id: branch-scope -->
|
|
## 범위
|
|
|
|
> 본 노트 = **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-<claim>` 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` |
|
|
|
|
## 마주친 문제
|
|
|
|
- 아직 없음(문서 단계).
|
|
|
|
## 묶음
|
|
|
|
<!-- GENERATED: sources:start -->
|
|
- [[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]]
|
|
<!-- GENERATED: sources:end -->
|
|
|
|
<!-- GENERATED: branches:start -->
|
|
- [[raw/branch-notes/feature-keycloak-google-redirect-uri-policy]]
|
|
<!-- GENERATED: branches:end -->
|
|
|
|
> 본 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`).
|