Files
llm-wiki/raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role.md

197 lines
12 KiB
Markdown

---
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 부분만 남긴다.
<!-- section-id: branch-parent -->
## 부모 (필수)
[[raw/branch-notes/feature-keycloak-idp-brokering-google-client]]
<!-- GENERATED: branch-contract:start -->
<!-- section-id: branch-contract-packet -->
## 브랜치 계약 패킷
- **생성 시 프로젝트 개정**: `1`
- **패킷 스키마**: `contract_packet: 1`
- **완료 조건**: Google login·Keycloak user mapping 200과 application diff 0줄이 검증된다
<!-- 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-to-role mapper를 IdP brokering 구성의 하위 계약으로 적용한다 | [[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 -->
## 묶음
<!-- GENERATED: sources:start -->
- [[raw/official-docs/keycloak-identity-provider-mappers]]
<!-- GENERATED: sources:end -->
> 본 sub-sub-branch 는 leaf — 자식 자료 없음. Phase 3 P3A 실 구현 또는 외부 산출물 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.
### 오류 기록 (이 sub-sub-branch 작업 중 발생)
- (없음 — 현재 documented-only 단계)
### 면접 준비 (이 작업에서 나올 수 있는 면접 질문)
- (없음 — Phase 3 실 구현 단계에 누적)
<!-- section-id: branch-goal -->
## 목표
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:
<!-- section-id: branch-scope -->
## 범위
### 포함 범위
- **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`)