--- title: branch / feature-keycloak-idp-mappers-claim-to-role (IdP Mappers — Google claim → Keycloak role) source_type: branch-note status: raw id: BR-KEYCLOAK-CHILD-321B472C kind: branch-child project: keycloak-patterns-overview work_item: WI-KEYCLOAK-PATTERNS-OVERVIEW-015 inherits: [DEC-KEYCLOAK-PATTERNS-OVERVIEW-IDP-BROKERING-001@1] refines: [] overrides: [] depends_on: [WI-KEYCLOAK-PATTERNS-OVERVIEW-003] contract_packet: 1 branch: feature-keycloak-idp-mappers-claim-to-role parent_branch: feature-keycloak-idp-brokering-google-client related_projects: [keycloak-patterns] tags: [branch, keycloak-patterns, spa, idp-brokering, google-federation, idp-mappers, p2b] created: 2026-05-25 target_merge: status_label: in-progress contract_packet_sha256: 6c7b7381db7953e7ba85ecf9dfc998cce5e018e0f146a3caa4a34e978c45324a --- # branch: feature-keycloak-idp-mappers-claim-to-role (IdP Mappers — Google claim → Keycloak role) > Layer: `raw/branch-notes/` — [[raw/branch-notes/feature-keycloak-idp-brokering-google-client]]의 WI015 child branch. > 학습 노트. P2B는 `documented-only` 단계. > **정합 노트 (2026-07-14 감사)**: 본 노트의 attribute-mapping 내용(Attribute Importer / Sync Mode / `hd` / `email_verified`)은 형제 [[raw/branch-notes/feature-keycloak-google-claim-attribute-mapping]] 와 ~70% 겹친다 — 그쪽이 attribute-mapping **owner**. 본 노트의 고유 책임 = **claim → role (RBAC 인가)** 이며 §5 **deferred authZ 트랙**. /ingest 시 attribute 부분은 owner 를 인용하고 본 노트는 role 부분만 남긴다. ## 부모 (필수) [[raw/branch-notes/feature-keycloak-idp-brokering-google-client]] ## 브랜치 계약 패킷 - **생성 시 프로젝트 개정**: `1` - **패킷 스키마**: `contract_packet: 1` - **완료 조건**: Google login·Keycloak user mapping 200과 application diff 0줄이 검증된다 ### 상속한 프로젝트 결정 | 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-to-role mapper를 IdP brokering 구성의 하위 계약으로 적용한다 | [[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 | |---|---|---|---|---| 없음. ## 묶음 - [[raw/official-docs/keycloak-identity-provider-mappers]] > 본 sub-sub-branch 는 leaf — 자식 자료 없음. Phase 3 P3A 실 구현 또는 외부 산출물 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화. ### 오류 기록 (이 sub-sub-branch 작업 중 발생) - (없음 — 현재 documented-only 단계) ### 면접 준비 (이 작업에서 나올 수 있는 면접 질문) - (없음 — Phase 3 실 구현 단계에 누적) ## 목표 Google ID token claim을 Keycloak role로 매핑해서, **SPA / backend가 Google 출신 사용자를 Keycloak local 사용자와 동일 RBAC 모델에서** 다룰 수 있게 한다. Profile attribute 매핑은 [[raw/branch-notes/feature-keycloak-google-claim-attribute-mapping]]이 정본이다. 면접 질문: "Google에서 받은 사용자 정보를 backend가 어떻게 보나요?" → "Keycloak의 IdP role mapper로 Google claim(예: `hd`)을 role로 변환합니다. Profile attribute는 별도 owner가 매핑하고, backend는 최종 Keycloak access token의 role만 소비합니다." - 이슈: - PR: ## 범위 ### 포함 범위 - **Hardcoded Role / Claim to Role / Advanced Claim to Role**: Google claim 값에 따른 role 부여. - Google `hd` claim 기반 role 분기와 개인 Gmail(`hd` 부재) 처리 정책. - **role mapper instance-level `Sync Mode Override = FORCE`**: IdP-level default를 바꾸지 않고 role freshness만 갱신(D1). ### 제외 범위 - Google profile claim → user attribute, Username Template, picture 전파, IdP-level default Sync Mode → [[raw/branch-notes/feature-keycloak-google-claim-attribute-mapping]] D2/D3/D4. - `email_verified=false` link 정책 → [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D4. 현재 범위는 silent auto-link 차단이며 hard-reject SPI는 별도 variant다. - 코드 변경 없음 검증 — [[raw/branch-notes/feature-keycloak-federation-spa-zero-change]] - JWT signature 검증 메커니즘 — [[raw/branch-notes/feature-keycloak-three-leg-trust-chain]] - Account Linking 흐름 — [[raw/branch-notes/feature-keycloak-account-linking-spa-ux]] ## TODO - [ ] Role mapper 종류표 작성 — Hardcoded Role / Claim to Role / Advanced Claim to Role — 등급: `documented-only` - [ ] Google `hd` claim → Advanced Claim to Role mapper 설정 (예: `hd=example.com`이면 `internal-user` role 부여) — 등급: `documented-only` - [ ] IdP-level default `IMPORT`를 유지하면서 **role mapper instance에만 `Sync Mode Override=FORCE`** 설정 — 등급: `planned` - [ ] `hd` 미존재 시 (개인 Gmail 계정) 처리 정책 — 등급: `planned` ## 진행 중 메모 - Profile attribute와 token claim 전파는 [[raw/branch-notes/feature-keycloak-google-claim-attribute-mapping]] D4 — 본 노트는 그 값을 재명세하지 않는다. - `hd` claim은 **Google Workspace 계정에만 존재**. 개인 Gmail 계정은 `hd` 없음. 따라서 "hd 없으면 거부"는 **사내 SaaS** 용도 (학습 노트에선 미적용). - IdP-level default는 [[raw/branch-notes/feature-keycloak-google-claim-attribute-mapping]] D3의 `IMPORT`다. 본 노트의 `FORCE`는 **role mapper instance-level override**로만 적용한다. ## 결정 사항 (decisions) - 2026-07-18: **role mapper instance-level `Sync Mode Override = FORCE`** — role freshness만 갱신한다. IdP-level default는 [[raw/branch-notes/feature-keycloak-google-claim-attribute-mapping]] D3의 `IMPORT`를 유지한다. - 2026-07-18: `email_verified=false` 정책은 [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D4를 consume한다. 현재 custom SPI artifact가 없으므로 전체 hard-reject를 이 branch에서 주장하지 않는다. - 2026-05-25: **`hd` claim 미적용** — 학습 단계는 개인 Gmail도 허용. 운영 SaaS 도입 시 Advanced Claim to Role로 `hd=example.com → internal-user` 매핑 추가. - 2026-05-25 (delegated): `picture` 전파는 [[raw/branch-notes/feature-keycloak-google-claim-attribute-mapping]] D4가 소유한다. ## 마주친 문제 - (학습 단계, 미실행) ## 관련 일일 노트 ## 근거 (필수, 최소 1개+) - [[raw/official-docs/keycloak-identity-provider-mappers]] - [[raw/official-docs/keycloak-identity-provider-sync-mode-official]] - [[raw/official-docs/google-oidc-discovery-spec]] ## 결정-근거 매핑 > 각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시. `Decision ID` 는 본 branch-note 안에서 안정적으로 유지. | Decision ID | Decision | Supporting Claims | Evidence Strength | Open Risk | |---|---|---|---|---| | D1 | **role mapper instance-level `Sync Mode Override = FORCE`** — IdP-level default `IMPORT`와 적용 계층을 분리 | `raw/official-docs/keycloak-identity-provider-sync-mode-official.md#KC-SYNCMODE-C4` (`force` = each login update), `#KC-SYNCMODE-C5` (mapper-level override) + [[raw/branch-notes/feature-keycloak-google-claim-attribute-mapping]] D3 — IdP-level default `IMPORT` | `official-vendor-doc + delegated` | role mapper에서 override가 노출되고 IdP default보다 우선하는지는 realm export/Admin UI 실측 전까지 `needs-confirmation` | | D2 | **DELEGATED** — [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D4 — 현재 정책은 silent auto-link 차단 | owner D4 | `delegated` | `email_verified=false` 전체 hard-reject는 구현된 custom SPI가 있는 별도 variant에서만 가능하며 현재 artifact 없음 | | D3 | `hd` claim 미적용 (학습 단계 — 개인 Gmail 허용, 운영 SaaS 전환 시 Advanced Claim to Role 매핑 추가) | `raw/official-docs/google-oidc-discovery-spec.md#GOOGLE-OIDC-C7` (`hd` 는 Workspace/Cloud organization 도메인 — personal Google account 의 `hd` 부재는 인용에 명시 없음 단서) | `official-vendor-doc` | personal Gmail 의 `hd` claim 부재 시 Keycloak mapper 동작 (null / 없음 / 거부) 의 정확한 검증은 별도 필요 — `GOOGLE-OIDC-C7` "Does not prove" 단서 | | D4 | **DELEGATED** — [[raw/branch-notes/feature-keycloak-google-claim-attribute-mapping]] D4 — profile attribute 매핑 owner | owner D4 | `delegated` | 본 role branch에서 attribute/token 전파 값을 재명세하지 않음 | ## 구현 가이드 | 단계 | 이 branch의 설정 | 완료 조건 | |---|---|---| | 1 | IdP-level default는 [[raw/branch-notes/feature-keycloak-google-claim-attribute-mapping]] D3의 `IMPORT`를 그대로 사용 | realm export에서 IdP 기본값이 `IMPORT`임을 확인 | | 2 | Google IdP 아래 role mapper instance에만 `Sync Mode Override=FORCE`를 적용 | realm export에서 해당 mapper의 override만 `FORCE`임을 확인 | | 3 | 운영 SaaS variant에서만 `hd=example.com` 조건의 Advanced Claim to Role mapper를 추가 | Workspace 계정과 개인 계정의 최종 Keycloak role 차이를 token으로 검증 | 현재는 `documented-only`다. Admin UI 캡처, realm export, 로그인 2회 후 role 갱신 증거가 모이기 전에는 구현 완료로 승격하지 않는다. ## 엣지·실패·의존 | 구분 | 조건 | 처리 / owner | |---|---|---| | Edge | 개인 Google 계정에 `hd`가 없을 수 있음 | 학습 variant에서는 로그인 자체를 거부하지 않고 도메인 기반 role만 부여하지 않는 정책을 검증한다 | | Failure | `FORCE`를 IdP-level default로 잘못 적용 | profile attribute까지 매 로그인 갱신되는 범위 확장을 피하고, role mapper instance override로 되돌린다 | | Dependency | profile attribute와 IdP default Sync Mode | [[raw/branch-notes/feature-keycloak-google-claim-attribute-mapping]] D3/D4 | | Dependency | `email_verified=false` 연결 정책 | [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D4 | ## 검증해야 할 주장 > 공식 문서나 사례는 근거지만, 내 프로젝트에서의 동작을 자동으로 보장하지 않는다. | Claim | Why uncertain | How to verify | Status | |---|---|---|---| | role mapper instance-level `Sync Mode Override=FORCE`가 IdP default `IMPORT`보다 우선하는지 | 적용 계층은 공식 근거가 있으나 배포 버전 realm export/UI 실측 없음 | role mapper만 FORCE로 설정하고 2회 로그인 후 role 갱신과 profile attribute 보존을 함께 확인 | `needs-confirmation` | | Advanced Claim to Role mapper 가 `hd=example.com` 일 때만 `internal-user` role 부여 (운영 SaaS 시) | `KC-IDP-MAPPER-C4` 가 명시적으로 `needs-confirmation` — mapper 종류 verbatim 부재 | Keycloak Admin UI 의 IdP → Mappers → Add mapper → Advanced Claim to Role 캡쳐 + 실제 등록 후 `hd` 별 token 발급 → role 차이 확인 | `planned` | | `email_verified=false` 전체 hard-reject variant | 현재 custom SPI provider/JAR/flow export가 없음 | 별도 SPI branch를 만들 때 provider artifact + realm flow export + negative E2E로 검증 | `deferred` | ## 완료 후 정리 > 학습 노트. P2B는 `documented-only` 유지. - PR 링크: - 리뷰 메모: - 머지 결과 / 배포 환경: 학습 노트 (`documented-only`) - **wiki 추출 대상**: - `actually-implemented` 항목: (없음) - `locally-verified` 항목: (없음) - `prod-verified` 항목: (없음) - **추출하지 않을 항목**: 전 항목 (`documented-only`)