31 KiB
title, source_type, status, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, branch, parent_branch, related_projects, tags, created, target_merge, status_label, contract_packet_sha256
| title | source_type | status | id | kind | project | work_item | inherits | refines | overrides | depends_on | contract_packet | branch | parent_branch | related_projects | tags | created | target_merge | status_label | contract_packet_sha256 | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| branch / feature-keycloak-three-leg-trust-chain (3-leg trust chain — Browser ↔ Keycloak ↔ Google 검증 메커니즘) | branch-note | raw | BR-KEYCLOAK-CHILD-11A28CFF | branch-child | keycloak-patterns-overview | WI-KEYCLOAK-PATTERNS-OVERVIEW-015 |
|
|
1 | feature-keycloak-three-leg-trust-chain | feature-keycloak-idp-brokering-google-client |
|
|
2026-05-25 | in-progress | 1af4709799789103babb4b3503ffefc67f35e51a4eb8c5175c4928267a419143 |
branch: feature-keycloak-three-leg-trust-chain (3-leg trust chain 검증)
Layer:
raw/branch-notes/— raw/branch-notes/feature-keycloak-idp-brokering-google-client의 WI015 child branch. 학습 노트. P2B는documented-only단계.
본 노트의 역할 (2026-07-18
/branch-spec정리): 본 노트는 부모 P2B(raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation) 가 hop 별 검증 매트릭스의 owner 로 위임한 결정(D5)의 정본이다(부모 §신뢰 경계 delegation). 부모는 이 D5 를 consume 만 하며, 본 노트가 각 hop 의 누가·무엇을·어떻게 검증하는가 를 소유한다.
부모 (필수)
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을 사용한다 | Browser·Keycloak·Google의 hop별 trust verification에 적용한다 | 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 |
|---|
없음.
목표
P2A는 2-leg trust (Browser ↔ Keycloak)였으나, P2B는 3-leg trust (Browser ↔ Keycloak ↔ Google). 각 hop마다 누가 무엇을 검증하는지 명확히 정리.
면접 질문: "Google 로그인이 추가되면 신뢰 검증이 어떻게 늘어나나요?"
→ "OIDC 표준상 RP는 Google ID token의 signature·issuer·audience·expiry와 요청에 보낸 nonce 일치를 검증해야 합니다. 이 배치에서는 Keycloak이 RP 역할을 맡지만, target Keycloak 버전이 nonce를 자동 송신·대조하는 제품 동작은 아직 wire trace로 확인하지 않았습니다. 이후 Keycloak이 자체 서명한 access token을 발급하고 backend는 Keycloak issuer/JWKS만 신뢰합니다."
- 이슈:
- PR:
범위
포함 범위
- Hop 1: Google → Keycloak — Google ID token signature 검증
- Hop 2: Keycloak → SPA — Keycloak access token signature 발급
- Hop 3: SPA → Backend — backend의 Keycloak JWT 검증
- Issuer 검증 규칙:
- Google
iss=https://accounts.google.com— 정확히 이 문자열. (⚠️ 정정 2026-07-18: 원래 여기 "또는accounts.google.com— spec 상 둘 다 허용" 이라 적었으나 사실과 다르다. 아카이브 근거raw/official-docs/google-oidc-discovery-spec.md#GOOGLE-OIDC-C1의 discovery JSON 은iss를 정확히https://accounts.google.com로만 규정하고, 그 "Does not prove" 열이 bare-hostname alias 를 명시적으로 부인한다. §Claims To Verify CV1 참조.) - Keycloak
iss=https://kc.example.com/realms/{realm-name}
- Google
- JWKS rotation 정책 비교 (Google vs Keycloak)
- Keycloak이 Google jwks_uri를 캐시하는 방식
제외 범위
- 코드 변경 없음 검증 — raw/branch-notes/feature-keycloak-federation-spa-zero-change
- claim mapping — raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role
- Account Linking — raw/branch-notes/feature-keycloak-account-linking-spa-ux
- 발급 측
aud주입 + 백엔드 audience 검증 구현 — raw/branch-notes/feature-keycloak-spring-rs-audience-validator D1·D4 소유. 본 노트 Hop3-audcell 은 그 계약을 consume 만. iss문자열 byte-match 를 성립시키는KC_HOSTNAME고정 — raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch D1 소유. 본 노트 Hop3-isscell 의 전제.
근거 (필수, 최소 1개+)
| Source | 정당화하는 결정 |
|---|---|
| raw/official-docs/keycloak-first-broker-login-flow | First Broker Login Flow 존재 + account linking 시점 (D4) |
| raw/official-docs/spring-security-resource-server-jwt | 백엔드가 단일 issuer(Keycloak) JWKS 로 self-configure + iss/aud 검증 (D1, D5 Hop3, CV4) |
| raw/official-docs/google-oidc-discovery-spec | Google issuer 문자열 + RS256 + nonce Required + local validation (D5 Hop1, D3, CV1) |
| raw/official-docs/security-jwt-rfc-7519-validation | JWT aud MUST-reject / exp MUST-NOT-accept (D5 Hop1·Hop3 aud·exp) |
| raw/official-docs/google-oauth2-redirect-uri-validation-official | redirect URI 정확 매칭 → redirect_uri_mismatch (CV7) |
| raw/official-docs/openid-connect-core-id-token-validation | OIDC Core 1.0 §2/§3.1.2.1/§3.1.3.7 — ID Token aud=RP client_id + nonce 검증 mandatory (OIDC-CORE-C1~C5; D5 Hop1-aud·nonce, D3) |
| raw/official-docs/jwks-keycloak-key-rotation-active-passive | Keycloak 이 자체 active key pair 로 새 서명 생성 (KC-ROT-C1) — D5 Hop2(Keycloak→SPA 재서명) 근거로만. ⚠️ 아래 disambiguation |
인용 범위 한정 (disambiguation):
raw/official-docs/jwks-keycloak-key-rotation-active-passive.md(KC-ROT-C*) 는 Keycloak 자신의 realm 서명키 active-passive 회전이다 — D5 Hop2(Keycloak 이 자체 키로 재서명)의 근거로는 유효하나, 외부 Google JWKS 캐시(D2)와는 무관하다. D2 근거로 인용하면 파일명의 "JWKS" 에 낚인FILENAME_INFERENCE오용이다.
TODO
- 3-leg 다이어그램 작성 (각 hop의 검증 항목 표기) — 등급:
documented-only(§구현 가이드 §1 매트릭스로 승격) - Google ID token 검증 항목 정리: signature(RS256, JWKS),
iss,aud,exp,nonce— 등급:documented-only(§구현 가이드 §1, cell 별 근거) - Google
iss허용값 — 정정 완료:https://accounts.google.com단일 (bare-hostname alias 는 spec 부인). 근거#GOOGLE-OIDC-C1— 등급:documented-only(CV1 resolved-as-contradiction) - Keycloak access token 검증 항목 정리 (backend 측): signature,
iss,aud,exp— 등급:documented-only(§구현 가이드 §1 Hop3,SSRS-JWT-C1/C2/C6). ※azp/typ=Bearer는 본 노트 Sources 에 직접 인용 없음 → 형제 audience-validator 매트릭스 소관 - Google JWKS rotation 빈도 (수일~수주 주기, 정확한 SLA 없음) — 등급:
needs-confirmation(CV2, 아카이브 부재 — live header 관찰 필요) - Keycloak이 Google jwks_uri를 캐시하는 정책: 기본 캐시 TTL, expired key fallback — 등급:
needs-confirmation(CV3/D2, 아카이브 부재 — Keycloak IdP config doc/소스 필요) - 함정: Keycloak이 Google JWKS 캐시 갱신 실패 시 Google 로그인 전체 장애 → fallback 정책 확인 — 등급:
needs-confirmation(§엣지·실패·의존) - 함정: backend가 Keycloak issuer를 잘못 적으면 모든 Google-originated token 거부 — 등급:
documented-only(CV4 resolved,SSRS-JWT-C6— 단 local 재현은planned)
진행 중 메모
- 핵심 통찰: backend는 Google을 모른다. backend의 JWT validation 코드는 Keycloak issuer / Keycloak JWKS만 본다. Google 추가/제거는 backend 코드에 영향 없음.
- 누가 무엇을 검증하나 정리 (§구현 가이드 §1 로 명세 승격 — 아래는 원본 통찰 보존):
| Hop | 검증자 | 검증 대상 | 검증 항목 |
|---|---|---|---|
| Google → Keycloak | Keycloak | Google ID token | signature(RS256, Google JWKS), iss, aud=Keycloak이 보유한 Google client_id, exp, nonce |
| (Keycloak 내부) | Keycloak | First Broker Login Flow 정책 | email_verified, hd, Account Linking 결정 |
| Keycloak → SPA | (Keycloak이 발급) | Keycloak access token | (Keycloak이 RS256 서명) |
| SPA → Backend | Backend | Keycloak access token | signature(Keycloak JWKS), iss=https://kc/realms/{r}, aud=<backend-client-id>, exp |
- Google JWKS 키 갱신 빈도가 빠른 편. spec에는 정해진 SLA 없음. Keycloak이 캐시한 키가 만료되었을 때 자동 refetch 필요.
- redirect URI 변조 방지: Google Cloud Console에 등록된 redirect URI 외 거부. Keycloak broker endpoint URL 변경 시 Google에도 반영 필요.
nonce검증 계약: OIDC RP는 요청의 nonce와 ID token nonce를 대조해야 한다. Keycloak이 이를 자동 송신·대조하는 target-version 제품 동작은needs-confirmation이며 HAR/source 확인 전 사실형으로 표현하지 않는다.- 2026-07-18 (
/branch-spec채움 pass) — pre-template(2026-05-25) 노트를 템플릿 정합으로 보강:선택 조건(R2) 열 추가 ·## 구현 가이드(§1 Hop별 검증 계약 · §2 issuer/audience 문자열 정합) ·## 엣지·실패·의존추가 · Sources 1→6 확장 · 섹션 순서 템플릿 정렬(Cluster 하단·Sources 상단). NO_GROUND_TRUTH — keycloak-patterns 는 실 구현 repo(/home/donghyeon/workspace/keycloak-patterns/, 부재 확인)가 없어 전 항목documented-only/needs-confirmation(코드 grep 불가). ca-tmpl 은 별개 프로젝트지만APP_SECURITY_JWT_ISSUERenv-key +AUTH_ISSUER_MISMATCH에러코드가 D1/CV4 의 "단일 issuer" 원리를 실물로 예시(cross-project 참고, 본 노트 등급엔 미반영). - 자동조사 —
wiki-research-lane로 기존 아카이브 8+5 문서를 재앵커링: D1 + D5 의 12 cell 중 8개가 official 근거 획득(이전엔 D4 외 전부UNSUPPORTED_DECISION). 신규 조사 1건(OIDC Core spec 아카이브)으로 Hop1-aud/noncecell 을 승격. deferred 3건(Keycloak 외부 JWKS 캐시·Google rotation cadence·Keycloak 기본 서명 알고리즘 — 아카이브 문서로 안 닫히는 runtime/config-empirical 항목, §Claims To Verify 로 위임).
결정 사항 (decisions)
- 2026-05-25: backend는 Keycloak JWKS만 신뢰 — Google JWKS는 backend 측에서 절대 검증하지 않음. 이유: 신뢰 경계 단순화. backend 입장에서 IdP는 Keycloak 하나.
- 2026-05-25: Google JWKS rotation은 Keycloak 책임 — Keycloak realm export / restore 시 캐시 초기화될 수 있음. 운영 시 모니터링 항목.
- 2026-05-25 (정합 2026-07-18):
nonce검증은 OIDC RP 의무다. Keycloak 자동 처리 및 비활성화 옵션 존재 여부는 target version에서 미검증이다.
결정-근거 매핑
본 branch 의 결정-근거 매핑.
선택 조건열(R2, 2026-07-18/branch-spec추가): "이 조건일 때 이 결정, 다른 조건이면 어떤 대안". 분기 없는 원리 진술은N/A. 2026-07-18 재앵커링: 이전 판은 D4 외 전부UNSUPPORTED_DECISION이었다(당시 Source 가keycloak-first-broker-login-flow1개뿐).wiki-research-lane가 기존 아카이브에서 D1·D5(8/12 cell)·D3(일부)의 직접 근거를 발굴 + OIDC Core 신규 아카이브로 Hop1-aud/nonce 를 닫아 재mapping 했다. D2 와 D5 의 4 cell 은 genuine gap 으로 남아 라벨 유지.
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|---|
| D1 | backend 는 Keycloak JWKS 만 신뢰 (Google JWKS 는 backend 측에서 절대 검증하지 않음) | 백엔드가 JWT 를 직접 검증하는 Resource Server(P2B)면 단일 issuer(Keycloak)만 신뢰가 기본 — issuer-uri 한 줄이 정확히 1개 AS 로 self-configure. 대안(백엔드가 Google JWKS 도 검증)은 신뢰 경계를 2개로 늘려 federation 추가/제거마다 백엔드 변경 유발 → 기각. 백엔드가 JWT 자체를 안 보는 구성이 필요하면 배치를 Edge forward-auth(P1B)로 바꿈(별도 패턴) |
raw/official-docs/spring-security-resource-server-jwt.md#SSRS-JWT-C1 (issuer-uri 로 self-configure + iss 검증), raw/official-docs/spring-security-resource-server-jwt.md#SSRS-JWT-C2 (4단계 deterministic discovery — 단일 AS 의 jwks_url) |
official-vendor-doc |
multi-issuer 는 JwtIssuerAuthenticationManagerResolver 별도(범위 밖). "신뢰 경계 단순화" 는 이제 mechanism 근거 보유(이전 UNSUPPORTED 해소). cross-project 실물 예시: ca-tmpl APP_SECURITY_JWT_ISSUER(단일 issuer URI 필수) + AUTH_ISSUER_MISMATCH 에러코드 |
| D2 | Google JWKS rotation(=외부 IdP JWKS 신선도)은 Keycloak 책임 (realm export/restore 시 캐시 초기화 가능, 운영 모니터링 항목) | Keycloak 이 broker 로서 Google id_token 을 검증하는 모든 P2B 구성 — 대안 없음: 백엔드가 Google 을 안 보므로(D1 의 따름) Google JWKS 신선도는 구조상 Keycloak 만 담당 가능. 단 캐시 TTL/fallback 동작은 미검증 | UNSUPPORTED_DECISION (Keycloak 의 외부 IdP JWKS 캐시 정책/TTL/fallback 은 아카이브 부재 — wiki-research-lane 가 keycloak-identity-brokering-overview-official·keycloak-identity-broker-spi·keycloak-import-export-realms 3개 추가 확인했으나 어느 것도 미기술. jwks-keycloak-key-rotation-active-passive.md 는 Keycloak 자체 realm 키 회전이라 D2 근거 아님 — 위 disambiguation) |
UNSUPPORTED_DECISION | 캐시 refetch 실패 시 Google 로그인 전체 5xx(§엣지·실패·의존). monitoring alarm 설계 전 Keycloak IdP config 페이지 또는 소스(OIDCIdentityProvider) 확인 의무 — §Claims To Verify CV3 |
| D3 | OIDC RP는 nonce를 송신하고 ID Token의 nonce 일치를 검증해야 한다. target Keycloak의 자동 처리 여부는 별도 제품 검증 | Google brokering의 replay 방지 계약. 표준 의무와 특정 제품 동작을 분리한다 | raw/official-docs/google-oidc-discovery-spec.md#GOOGLE-OIDC-C5, raw/official-docs/openid-connect-core-id-token-validation.md#OIDC-CORE-C2, #OIDC-CORE-C5 |
official-standard (의무) / needs-confirmation (Keycloak 제품 동작) |
Keycloak 자동 송신·대조와 설정 토글은 HAR/source 확인 전 외부 주장 금지 |
| D4 | Keycloak 의 First Broker Login Flow 가 외부 IdP (Google) 로 첫 로그인 시 account linking 정책을 실행한다는 일반 진술 | N/A (원리 진술 — account linking 의 정확한 정책은 owner 형제 raw/branch-notes/feature-keycloak-first-broker-login-flow 소관) | raw/official-docs/keycloak-first-broker-login-flow.md#KC-FBL-C1 (First login flow 존재 + sub-section 구조) |
official-vendor-doc |
KC-FBL-C2~C4 는 needs-confirmation — verbatim 재검증 보류. account linking 의 정확한 동작(자동 link vs prompt)은 owner 형제 소관 |
| D5 | Hop 별 검증 매트릭스 (Hop1 Google→Keycloak / Hop2 Keycloak→SPA / Hop3 SPA→Backend) — 부모 P2B 가 위임한 owner 결정 | N/A (검증 계약 명세, 분기 아님). 단 각 hop 검증 주체는 배치 의존: P2B(SPA direct)면 Hop3 검증자=백엔드 Resource Server, P1B(edge proxy)면 oauth2-proxy 대행(형제 패턴) | cell 별(§구현 가이드 §1 표): Hop1-sig #GOOGLE-OIDC-C3·#GOOGLE-OIDC-C8; Hop1-iss #GOOGLE-OIDC-C1; Hop1-aud security-jwt-rfc-7519-validation.md#JWT-RFC7519-C1(generic) + openid-connect-core-id-token-validation.md#OIDC-CORE-C1/#OIDC-CORE-C4; Hop1-exp #JWT-RFC7519-C2; Hop1-nonce #GOOGLE-OIDC-C5+OIDC-CORE-C2/#OIDC-CORE-C5; Hop2 jwks-keycloak-key-rotation-active-passive.md#KC-ROT-C1(자체 active key 서명); Hop3-sig spring-security-resource-server-jwt.md#SSRS-JWT-C1·#SSRS-JWT-C2; Hop3-iss #SSRS-JWT-C1·#SSRS-JWT-C6; Hop3-aud #SSRS-JWT-C6; Hop3-exp #JWT-RFC7519-C2 |
official-standard + official-vendor-doc (8/12 cell) |
4 cell 미해소(§구현 가이드 §1 UNSUPPORTED_IMPL_DECISION): ① Hop1-aud 의 Google-specific "aud=Keycloak client_id"(generic RFC + OIDC Core 로 원리는 닫히나 Google 명시 quote 부재), ② Hop2 default 알고리즘 RS256(KC-ROT-C1 은 "자체 active key 서명"만 증명, RS256 명시 없음), ③ Keycloak-side nonce 자동 동작(D3 와 동일 gap). §Claims To Verify |
구현 가이드
본 branch 는
documented-only학습 노트 — 실 구현 코드가 아니라 각 hop 의 검증 계약(누가·무엇을·어떻게 검증하는가)의 사전 명세다. 부모 P2B 가 이 §의 매트릭스를 hop 검증 owner 로 위임했다. 아래 sub-section 은 본 branch 결정(D1·D3·D5)에서만 도출한다.3-rule: R1 각 cell 은 Decision ID + Claim ID reference. R2 근거 없는 detail 은
UNSUPPORTED_IMPL_DECISION라벨 + trade-off. R3 본 branch 범위 밖(발급측 aud 주입·KC_HOSTNAME 고정)은 owner 형제로 위임(§엣지·실패·의존 다른 계약 의존).
1. Hop별 검증 계약 (verifier → 대상 → 항목 → 근거)
Trace: D5(전 cell) + D1(Hop3 단일 issuer 신뢰). 각 cell 은 아래 표의 Claim ID 로 근거. §진행 중 메모의 "누가 무엇을 검증하나" 통찰을 명세로 승격.
- UNSUPPORTED_IMPL_DECISION:
- Hop1-aud (Google-specific): "Google id_token 의
aud= Keycloak 이 등록한 Google client_id" 의 Google 명시 verbatim 은 아카이브에 없음. generic 근거(JWT-RFC7519-C1aud MUST-reject) + OIDC Core(ID Token aud=RP client_id)로 원리는 닫히나, "Google 이 그렇게 발급한다" 는INFERENCE. trade-off: OIDC RP-client 표준 의미상 거의 확실하나 FACT 승격은 Google Identity 페이지 또는 실 토큰 decode 필요(§Claims To Verify).- Hop2 서명 알고리즘 (RS256):
KC-ROT-C1은 "Keycloak 이 자체 active key pair 로 새 서명 생성"만 증명 — default 알고리즘이 RS256 이라는 근거는 아카이브 부재. trade-off: Keycloak 관례상 RS256 이 default 로 알려져 있으나 미검증 → 이 cellneeds-confirmation.- Hop1-nonce (Keycloak-side): nonce 의 필요/검증 원리는 spec(§2 근거), 그러나 Keycloak 이 자동으로 송신/대조하는지는 미아카이브(D3 Open Risk 와 동일).
| Hop | 검증자 | 대상 | 검증 항목 | 근거 (Claim ID) | cell 등급 |
|---|---|---|---|---|---|
| Hop1 Google→Keycloak | Keycloak | Google id_token | signature RS256 (Google JWKS, local 검증) | #GOOGLE-OIDC-C3(RS256-only), #GOOGLE-OIDC-C8(retrieve keys + validate locally) |
documented-only |
| Hop1 | Keycloak | Google id_token | iss = https://accounts.google.com (정확 문자열) |
#GOOGLE-OIDC-C1 |
documented-only |
| Hop1 | Keycloak | Google id_token | aud = Keycloak 의 Google client_id |
#JWT-RFC7519-C1(generic aud MUST-reject) + OIDC-CORE-C1/#OIDC-CORE-C4(ID Token aud=RP client_id) |
⚠️ UNSUPPORTED_IMPL_DECISION (Google-specific quote 부재) |
| Hop1 | Keycloak | Google id_token | exp (만료 검증, clock-skew leeway) |
#JWT-RFC7519-C2 |
documented-only |
| Hop1 | Keycloak | Google id_token | nonce 일치 (replay 방지) |
#GOOGLE-OIDC-C5(nonce Required) + OIDC-CORE-C2/#OIDC-CORE-C5 |
⚠️ UNSUPPORTED_IMPL_DECISION (Keycloak 자동 동작 미검증) |
| Hop2 Keycloak→SPA | (Keycloak 발급) | Keycloak access_token | 자체 active key 로 재서명 | #KC-ROT-C1 (single active key pair → new signatures) |
⚠️ needs-confirmation (알고리즘 RS256 명시 부재) |
| Hop3 SPA→Backend | Backend RS | Keycloak access_token | signature (Keycloak JWKS, discovery) | #SSRS-JWT-C1, #SSRS-JWT-C2 |
documented-only |
| Hop3 | Backend RS | Keycloak access_token | iss = https://kc/realms/{r} (byte-match) |
#SSRS-JWT-C1, #SSRS-JWT-C6("iss 가 아니면 validation fail") |
documented-only |
| Hop3 | Backend RS | Keycloak access_token | aud = backend-client-id |
#SSRS-JWT-C6 (audiences property → aud 검증) |
documented-only (발급측 aud 주입은 형제 audience-validator D4 의존) |
| Hop3 | Backend RS | Keycloak access_token | exp |
#JWT-RFC7519-C2 |
documented-only |
2. audience 문자열 정합 규칙 (byte-match + 발급측 선행)
Trace: D1 + D5 Hop3-iss(
#SSRS-JWT-C1·#SSRS-JWT-C6) + Hop1-iss(#GOOGLE-OIDC-C1).
- UNSUPPORTED_IMPL_DECISION: 없음 — 아래는 인용 claim 의 직접 도출.
| 항목 | 규칙 | 근거 | 의존(owner 형제) |
|---|---|---|---|
Google iss |
정확히 https://accounts.google.com — bare-hostname alias 금지(비교 실패) |
#GOOGLE-OIDC-C1 (+ Does-not-prove: alias 부인) |
— (CV1 correction) |
Keycloak iss (Hop3) |
백엔드 issuer-uri == token iss byte-level 일치. discovery 로 self-configure, 불일치 시 validation fail |
#SSRS-JWT-C1, #SSRS-JWT-C6 |
raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch D1 (KC_HOSTNAME 고정으로 iss 안정화) |
Keycloak aud (Hop3) |
aud 에 backend-client-id 포함해야 통과. 발급측이 안 넣으면 aud=account 만 → 정상 토큰도 거부 |
#SSRS-JWT-C6 |
raw/branch-notes/feature-keycloak-spring-rs-audience-validator D4 (SPA client Audience mapper 선행) |
| Google redirect URI | Keycloak broker endpoint 를 Google Console 에 정확 등록 — 불일치 시 redirect_uri_mismatch |
raw/official-docs/google-oauth2-redirect-uri-validation-official.md#GOOGLE-REDIR-C3 |
raw/branch-notes/feature-keycloak-google-redirect-uri-policy D1~D4 (exact-match 규칙 deep owner) + raw/branch-notes/feature-keycloak-idp-brokering-google-client D3 (등록 step) |
엣지·실패·의존
R4 캡처용. 본 branch 는
documented-only이나, 3-leg trust 를 실제로 세울 때 부딪힐 실패/엣지와 다른 계약 의존을 미리 열거.
-
실패·엣지 경로:
- Keycloak → Google JWKS refetch 실패 → Google 로그인 전체 5xx (가장 위험, 운영): Keycloak 이 Google
jwks_uri를 못 가져오면(네트워크 단절 / Google 측 변경) Hop1 signature 검증 불가 → 모든 Google 로그인 실패. 캐시 TTL·expired-key fallback 동작 미검증(D2/CV3UNSUPPORTED). 기대 동작: 만료 시 자동 refetch(가정), monitoring alarm 필수. - 백엔드 issuer 오설정 → 모든 Google-originated token 거부: 백엔드
issuer-uri가 Keycloak realm URL 과 byte-level 불일치면 Hop3 에서iss검증 실패 → Google 로 로그인한 사용자 포함 전 토큰 401. 근거#SSRS-JWT-C6("iss 가 아니면 validation will fail"). 기대 동작: 의도적 오설정 시 401 (CV4, local 재현planned). - Google
issalias 혼동 → Hop1 검증 실패:accounts.google.com(bare)로 비교하면https://accounts.google.com발급 토큰이 불일치. 근거#GOOGLE-OIDC-C1(정확 문자열). CV1 정정 사항 — 과거 "둘 다 허용" 은 folklore. - nonce 미검증/재사용 → replay 취약: Hop1 에서 nonce 대조를 안 하면 탈취된 id_token 재생 가능(D3). 단 Keycloak 자동 검증 여부 미검증.
- redirect URI 변조 → Google 거부: Keycloak broker endpoint 외 URI 는
redirect_uri_mismatch(#GOOGLE-REDIR-C3). Keycloak broker URL 변경 시 Google Console 반영 필요. - Keycloak 서명 알고리즘 가정 오류: 백엔드가 RS256 을 가정하는데 Keycloak realm 이 다른 알고리즘이면 Hop3 signature 검증 실패. Hop2 알고리즘 default 는
needs-confirmation(§구현 가이드 §1).
- Keycloak → Google JWKS refetch 실패 → Google 로그인 전체 5xx (가장 위험, 운영): Keycloak 이 Google
-
다른 계약 의존:
- raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation (부모 P2B) — 부모가 hop 검증 매트릭스 owner 를 본 노트 D5 로 위임(부모 §신뢰 경계 delegation). 본 노트 D5 가 바뀌면 부모 §신뢰 경계 개요 갱신 필요(비차단 전파).
- raw/branch-notes/feature-keycloak-spring-rs-audience-validator D1(백엔드
iss+sig+exp+aud4종 검증) · D4(Keycloak Audience mapper 로 backend client_id 를aud에 주입) — 본 노트 Hop3-aud cell 이 그 계약을 consume. federation 환경에서도aud가 포함되는지는 §Claims To Verify CV5 로 그쪽에 위임. - raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch D1(
KC_HOSTNAME고정 → tokeniss가 백엔드issuer-uri와 byte-match) — 본 노트 Hop3-iss cell + D1 성립의 전제. iss 불일치 함정의 재현·해결은 그 branch 소관. - raw/branch-notes/feature-keycloak-google-redirect-uri-policy D1~D4(redirect URI exact-match / byte-level 규칙 deep owner, GOOGLE-REDIR 근거 계열) + raw/branch-notes/feature-keycloak-idp-brokering-google-client D3(Authorized redirect URI 등록 step) — 본 노트 Hop1 redirect URI 변조 방지(CV7,
#GOOGLE-REDIR-C3)가 이 계약에 의존.
검증해야 할 주장
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
[CV1 — resolved-as-contradiction] Google iss 가 https://accounts.google.com 와 accounts.google.com 둘 다 spec 상 정당한가 |
원래 "둘 다 허용" 으로 적었으나 아카이브가 반증 | ✅ 해소: raw/official-docs/google-oidc-discovery-spec.md#GOOGLE-OIDC-C1 — discovery JSON 은 iss 를 정확히 https://accounts.google.com 로만 규정, Does-not-prove 열이 alias 를 명시 부인. 본문 §마주친 문제의 "과거 사례" 는 미검증 folklore |
resolved (documented-only) |
| [CV4 — resolved] backend 가 Keycloak issuer 를 잘못 적으면 모든 Google-originated token 거부 | 메커니즘은 문서화, local 재현은 미실행 | ✅ 메커니즘 raw/official-docs/spring-security-resource-server-jwt.md#SSRS-JWT-C6("iss 가 아니면 validation will fail"). local 재현(issuer-uri 의도적 오설정 → 401)은 별도 planned |
documented-only (재현 planned) |
| [CV7 — resolved] Google Cloud Console redirect URI 정확 매칭(변조 방지) | Source 미등록이었음 | ✅ raw/official-docs/google-oauth2-redirect-uri-validation-official.md#GOOGLE-REDIR-C3(정확 매칭, else redirect_uri_mismatch). Sources 에 등록 완료 |
documented-only |
| [CV2 — gap] Google JWKS rotation 빈도 + Cache-Control / SLA | 아카이브 부재 — google-openid-connect-oidc·google-oidc-discovery-spec 어디에도 cadence/헤더 없음 |
https://www.googleapis.com/oauth2/v3/certs 응답 헤더(Cache-Control: max-age=...) 직접 관찰 또는 Google 지원 페이지 아카이브 |
needs-confirmation (research opt-in) |
| [CV3/D2 — gap] Keycloak 의 외부 IdP(Google) JWKS 캐시 정책 (기본 TTL, expired-key fallback) | 아카이브 부재 — Keycloak IdP 문서 3개 확인했으나 미기술. KC-ROT 는 자체 키라 무관 |
Keycloak Identity Provider admin config 페이지 또는 소스(OIDCIdentityProvider/AbstractOAuth2IdentityProvider) 정독 후 raw/official-docs/ 등록 |
needs-confirmation (research opt-in) |
[CV5 — delegated] Keycloak 발급 access_token 에 aud=<backend-client-id> 포함(federation 환경에서도) |
본 노트 Sources 범위 밖 — 발급측 결정 | raw/branch-notes/feature-keycloak-spring-rs-audience-validator D4 + 실 토큰 decode. 본 노트는 Hop3-aud cell 로 consume | needs-confirmation (형제 위임) |
[CV6/D3 — gap] Keycloak 이 Google 요청 시 random nonce 동봉 + 응답 id_token nonce 자동 대조 |
spec 은 "RP 가 해야 한다"만 증명, Keycloak 자동 동작은 미아카이브 | Keycloak OIDC Identity Provider config doc(nonce/PKCE 토글) 또는 소스 OIDCIdentityProvider.createAuthenticationRequest(), 또는 wire trace(HAR) |
needs-confirmation (research opt-in) |
| [Hop2 — gap] Keycloak 기본 서명 알고리즘이 RS256 인가 | KC-ROT-C1 은 "자체 active key 서명"만 증명, 알고리즘 명시 없음 |
Keycloak realm "Keys" 탭 문서 또는 realm default-signature-algorithm provider config 확인 |
needs-confirmation |
마주친 문제
- (학습 단계, 미실행)
- 잠재적 함정 기록:
Google이 issuer 표기를⚠️ 정정(2026-07-18): 이 진술은 미검증 folklore 다. 아카이브 근거(https://accounts.google.com로도accounts.google.com로도 발급한 사례가 있음 (과거).#GOOGLE-OIDC-C1)는iss를 정확히https://accounts.google.com단일 문자열로 규정하고 alias 를 부인한다. 둘 다 유효했다는 2차 아카이브(예: 과거 Google OIDC discovery 스냅샷)가 나오기 전엔 "둘 다 허용" 으로 취급 금지. (CV1)- Keycloak이 Google JWKS를 가져오지 못하면 (네트워크 단절, Google 측 변경) Google 로그인 전체가 5xx. 모니터링 alarm 필요. (§엣지·실패·의존, D2/CV3)
묶음
본 sub-sub-branch 는 leaf — 자식 자료 없음. Phase 3 P3A 실 구현 또는 외부 산출물 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.
오류 기록 (이 sub-sub-branch 작업 중 발생)
- (없음 — 현재 documented-only 단계)
면접 준비 (이 작업에서 나올 수 있는 면접 질문)
- (없음 — Phase 3 실 구현 단계에 누적)
관련 일일 노트
완료 후 정리
학습 노트. P2B는
documented-only유지.
- PR 링크:
- 리뷰 메모:
- 머지 결과 / 배포 환경: 학습 노트 (
documented-only) - wiki 추출 대상:
actually-implemented항목: (없음)locally-verified항목: (없음)prod-verified항목: (없음)
- 추출하지 않을 항목: 전 항목 (
documented-only/needs-confirmation). 단 D2·CV2·CV3·CV6·Hop2 gap 종결 후wiki/concepts/keycloak-google-federation-trust-boundary(hop 검증 매트릭스) 합성 후보(현 statusdocumented-only로 파생 게이트 미달).