Files
llm-wiki/raw/project-notes/keycloak-patterns-overview.md

937 lines
76 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: keycloak-patterns Overview (canonical SSOT)
source_type: project-note
status: raw
confidence: medium
tags: [project-note, keycloak-patterns, oauth2, oidc, auth]
related_projects: [keycloak-patterns]
last_reviewed: 2026-07-14
diagrams: [keycloak-patterns/architecture-p1a-edge-no-google-2026-05-26, keycloak-patterns/architecture-p1b-edge-google-2026-05-26, keycloak-patterns/architecture-p2a-cluster-internal-no-google-2026-05-26, keycloak-patterns/architecture-p2b-cluster-internal-google-2026-05-26, keycloak-patterns/architecture-p3a-single-ec2-no-google-2026-05-26, keycloak-patterns/architecture-p3b-single-ec2-google-2026-05-26]
architecture_review: 2026-05-26
status_label: active
project_revision: 1
url:
semantic_surface_exclusions:
- artifact-registry|legacy hub has no project-local Artifact Registry; harness/source/typed-contracts.json is authoritative until migration
- contract-gate-registry|legacy hub has no project-local Contract/Gate Registry; harness/source/typed-contracts.json is authoritative until migration
- flow-stage-registry|legacy hub has no project-local Flow/Stage Registry; harness/source/typed-contracts.json is authoritative until migration
---
# keycloak-patterns Overview
> 본 문서는 keycloak-patterns 프로젝트의 **canonical SSOT** (Single Source of Truth)입니다.
> 모든 branch-note는 본 문서를 기준으로 작업하며, 본 문서가 정의하지 않은 결정은 sub-branch 내부에서 자체 결정.
>
> **위계 (CLAUDE.md §2/§15)**:
> - 본 문서: 프로젝트 전반 정의 (canonical SSOT)
> - `raw/branch-notes/feature-keycloak-patterns.md`: 작업 root (전체 진행 인덱스)
> - `raw/branch-notes/feature-keycloak-<pattern-name>.md`: 6 패턴별 sub-branch (예: `feature-keycloak-edge-forwardauth-no-google`)
> - `raw/branch-notes/feature-keycloak-<implementation-topic>.md`: 패턴별 세부 단계 sub-sub-branch (예: `feature-keycloak-oauth2-proxy-oidc-flow`)
> - 계층 정보는 frontmatter `parent_branch:` + 각 파일의 `## Parent` 섹션에서 추적
> - 구현 코드: `/home/donghyeon/workspace/keycloak-patterns/` (별도 git repo, LLM Wiki 외부)
## 1. 프로젝트 정의
### 한 줄 설명
vanilla JS 클라이언트 + Keycloak Authorization Server + Spring Boot 연동의 **4가지 인증 통합 아키텍처 패턴(AP1~AP4)** 을 비교·이해·구현하는 학습 + 구현 프로젝트. (분류축 교정 2026-07-14 — 기존 "6가지 배치×federation" 은 §2 로 재편; 배포 토폴로지·Google federation 은 각 패턴에 얹는 cross-cutting 변형.)
### 본인 역할
- 개인 프로젝트
- 본인이 맡은 영역: 전 영역 (인프라 + 백엔드 + 프론트 + Keycloak 운영 학습)
- 기간: 2026-05-25 ~ 미정
### 목표 (WHY)
면접에서 "왜 이 배치를 택했나" / "Google 로그인이 붙으면 흐름이 어떻게 바뀌나" / "BFF vs SPA Direct OIDC trade-off는?"에 자신 있게 답할 수 있는 수준의 이해 + **4 인증-아키텍처 패턴 실 구현**.
> **분류축 교정 (2026-07-14)**: 기존 목표는 "배치×federation 6패턴 이해 + P3A 한정 실 구현" 이었으나, 그 6축은 *배포 토폴로지 × Google* 이라 keycloak 인증 아키텍처를 2종만 exercise 하고 AP2·AP3 는 누락돼 있었다. §2 에서 primary 축을 **인증 통합 아키텍처 4패턴** 으로 교정하고, 이 4패턴 실 구현을 새 목표로 삼는다. 상세 근거·매핑은 §2.
### 성공 기준 (측정가능, R1)
> "잘 이해했다" 류 정성 표현 금지. 각 패턴이 "끝났다"고 말할 검증 가능한 결과로 정의한다. done-bar = **E2E 검증 + 그 패턴의 signature 함정 의도 재현 → 해결** (2026-07-14 사용자 확정).
**패턴 공통 done (4 패턴 각각 + Google cross-cutting 1회):**
1. **E2E**: 브라우저 로그인 → 토큰 발급 → 보호 API `200 OK` 를 로컬(`docker compose up`)에서 관찰 — curl 로그 또는 스크린샷 증거 첨부. 등급 `locally-verified`.
2. **Signature 함정 재현 → 해결**: 그 패턴의 대표 실패를 의도적으로 재현(4xx / 토큰 누출)한 뒤 고치고, before/after 를 기록.
| 패턴 | E2E 성공 신호 | 재현 → 해결할 signature 함정 |
|---|---|---|
| **AP1** SPA-direct + Resource Server | SPA 가 받은 access_token 으로 `/api` 200 | (a) `aud` 미검증 → 타 client 토큰 통과 재현 → audience validator 로 401 (b) `KC_HOSTNAME` 미설정 → `iss` mismatch 401 재현 → 설정으로 해결 |
| **AP2** Token-Mediating Backend | 백엔드(confidential client)가 발급받은 access_token 을 브라우저에 전달, 브라우저가 RS 직접 호출 200 | refresh token 이 브라우저에 **노출 안 됨**(백엔드만 보유) 을 네트워크 탭 / 응답 바디로 확인 |
| **AP3** BFF | 브라우저에 토큰 0개(session cookie 만) 확인, BFF proxy 경유 API 200 | CSRF surface(cookie 자동첨부) 재현 → SameSite / CSRF token 으로 차단 |
| **AP4** Edge forward-auth | 미인증 요청 → Keycloak redirect, 인증 후 backend 가 `X-Forwarded-User` 수신 200 | `X-Forwarded-User` 위조로 우회 재현 → NetworkPolicy / SG 로 차단 |
| (cross) **Google brokering** | Google 계정 로그인 → Keycloak 사용자 매핑 → 위 패턴 흐름 재개 200 | `email_verified=false` auto-linking 계정탈취 재현 → Confirm Link Existing Account 로 차단 |
**프로젝트 완료 신호**: 위 표의 4 패턴 + Google cross-cutting 이 모두 `locally-verified` + 트레이드오프 매트릭스 branch 가 4패턴의 {토큰 위치 · 검증 주체 · XSS/CSRF surface · 선택 기준 · keycloak 설정} 을 한 표로 답할 수 있음.
## 2. 인증 아키텍처 분류 (canonical 분류 축 — 2026-07-14 교정)
> **분류축 교정 (2026-07-14).** 기존 분류축은 *배치 위치 3 × Google federation 2 = 6 패턴* 이었으나, 이는 **배포 토폴로지 × federation** 축이라 keycloak *인증 아키텍처* 는 2종(edge-forward-auth, SPA-direct)만 exercise 하고 나머지는 변형이었다(P2≡P3 는 auth 동일, B=A+realm 설정). 멘토가 말한 "4 패턴" 은 **인증 통합 아키텍처**(누가 토큰을 쥐고, 누가 인증을 강제하나) 축이며, 이것이 keycloak client 통합의 canonical 축이다. 아래로 primary 축을 교체하고, 기존 6 축은 §2.2 cross-cutting 변형으로 강등한다.
>
> 근거: [[raw/official-docs/oauth2-browser-based-apps-ietf-draft]] (IETF — 브라우저앱 아키텍처 3종 BFF / Token-Mediating Backend / Browser-based OAuth Client 을 *보안강도 내림차순* 으로 정의), [[raw/company-tech-blogs/curity-bff-pattern-spa]].
### 2.1 Primary 축 — 인증 통합 아키텍처 4 패턴
축: **누가 access/refresh token 을 보관하고, 누가 인증을 강제하는가.** IETF `draft-ietf-oauth-browser-based-apps` 의 3 패턴 + 별도 인프라 프록시 강제(oauth2-proxy) = 4.
| ID | 패턴 | 토큰 위치 | 인증 강제 주체 | 백엔드 keycloak 역할 | 보안강도 (IETF) |
|----|------|----------|---------------|---------------------|-----------------|
| **AP1** | Browser-based OAuth Client (SPA-direct + Resource Server) | 브라우저(JS) | SPA 자신 (public client + PKCE) | Resource Server — JWKS 로 JWT 검증 | 낮음 (토큰 브라우저 노출) |
| **AP2** | Token-Mediating Backend | access → 브라우저, refresh → 백엔드 | 백엔드 (confidential client) 가 토큰 획득 후 access token 만 전달 | confidential client + RS | 중 |
| **AP3** | Backend-for-Frontend (BFF) | 백엔드 (session) | 백엔드 (confidential client), 모든 API proxy | confidential client + session holder | 높음 (토큰 브라우저 미노출) |
| **AP4** | Edge / Gateway forward-auth | 프록시 (session) | 별도 reverse proxy (oauth2-proxy / Traefik) | 프록시가 OIDC, 백엔드는 헤더 신뢰 (인증코드 0줄) | 프록시 network 격리에 의존 |
> IETF 보안강도 내림차순 = BFF(AP3) > Token-Mediating(AP2) > Browser-client(AP1). AP4 는 IETF 3종 밖(별도 인프라 프록시)이나 실무의 4번째 패턴.
### 2.2 Cross-cutting 변형 (별도 패턴 아님 — 각 AP 에 얹음)
- **배포 토폴로지**: single-EC2(학습·실 구현) / cluster-internal / edge. 인증 아키텍처를 바꾸지 않고 hostname·issuer·network 경계만 바꿈. signature 함정: `KC_HOSTNAME` iss mismatch, reverse-proxy 헤더.
- **Google IdP brokering (federation)**: realm 에 Google 을 외부 IdP 로 등록. 노트 실측대로 SPA/Backend **코드 0줄 변경** — 어느 AP 에도 동일하게 얹힘. signature 함정: First Broker Login email auto-linking.
### 2.3 기존 6 패턴 → 신 4 패턴 매핑 (기존 작업 재배치, 폐기 아님)
> 기존 34 branch-note 는 폐기하지 않고 아래로 re-map. 실제 rename/re-parent 은 `wiki-doc-author mode=migrate` 로 점진 수행(자동 mv 금지 — wikilink 영향 검토).
| 기존 (배치×federation) | 신 primary (auth 축) | 신 cross-cutting | 기존 sub-branch |
|---|---|---|---|
| P1A Edge no-google | **AP4** Edge forward-auth | 배포=edge | [[raw/branch-notes/feature-keycloak-edge-forwardauth-no-google]] |
| P1B Edge + Google | **AP4** | +Google brokering | [[raw/branch-notes/feature-keycloak-edge-forwardauth-google-federation]] |
| P2A Internal SPA-direct no-google | **AP1** SPA-direct + RS | 배포=internal | [[raw/branch-notes/feature-keycloak-internal-spa-direct-no-google]] |
| P2B Internal + Google | **AP1** | 배포=internal, +Google | [[raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation]] |
| P3A Single-EC2 no-google | **AP1** SPA-direct + RS | 배포=single-EC2 (실 구현 base) | [[raw/branch-notes/feature-keycloak-single-ec2-no-google]] |
| P3B Single-EC2 + Google | **AP1** | 배포=single-EC2, +Google | [[raw/branch-notes/feature-keycloak-single-ec2-google-federation]] |
| (없음) | **AP2** Token-Mediating Backend | — | 신규 — 기존 6 에 없던 패턴 |
| (bff-vs-spa-direct, out-of-scope 비교문서) | **AP3** BFF | — | [[raw/branch-notes/feature-keycloak-bff-vs-spa-direct]] → AP3 비교 근거(FOLD-IN); 실 구현은 신규 `feature-keycloak-bff-oauth2login-session`·`-bff-csrf-samesite-defense` |
**핵심**: 기존 "6 구현" 은 실제로 auth 아키텍처 2종(AP1, AP4)의 배포·federation 변형이었고 AP2·AP3 는 누락돼 있었다. 신 축은 중복(P2≡P3, B=A+federation)을 제거하고 누락(AP2, AP3)을 채운다 → "6 이 맞나 4 가 맞나" 의 답: **둘은 다른 축이었고, keycloak 을 다 배우려면 auth 축 4패턴이 맞다.**
## 3. 공통 컴포넌트 & 용어
- **Keycloak**: OIDC/OAuth2 Authorization Server. Realm / Client / User / Identity Provider 구성.
- **Client (vanilla JS / SPA)**: Authorization Code Flow + **PKCE**. client 유형은 패턴별로 다름 — **AP1 은 public client**(토큰 브라우저 보유), **AP2·AP3 은 백엔드가 confidential client**(client secret 보유, 토큰을 백엔드가 획득). AP4 는 SPA 가 아니라 프록시가 OIDC client.
- **Backend (API)**: Spring Boot. 패턴별 역할 상이 — AP1/AP4 는 Resource Server(JWT signature + `iss`/`aud`/`exp` 검증), AP2/AP3 는 confidential OAuth client(+ AP3 은 session holder + proxy).
- **Edge Proxy** (P1만): oauth2-proxy 또는 Traefik ForwardAuth — 인증 안 된 요청을 Keycloak으로 redirect, 인증 완료 시 backend로 통과.
- **IdP Brokering** (B 변형): Keycloak이 Google을 외부 IdP로 등록. 사용자 Google 계정으로 로그인 → Google → Keycloak 사용자 매핑 (First Broker Login Flow) → Keycloak token 발급.
- **Token 종류**:
- `authorization code`: 1회용 코드 (브라우저 redirect 매개)
- `access token` (JWT): API 호출용. 짧은 만료 (515분)
- `refresh token`: access token 갱신용. 긴 만료 (130일)
- `ID token` (JWT): 사용자 식별 정보. 백엔드는 보통 사용 안 함, 클라이언트가 사용자 표시용으로 사용.
<!-- section-id: architecture-components -->
## 3-1. 시스템 아키텍처 (System Architecture)
> 6개 패턴 각각의 컴포넌트 구성도. **`templates/diagram-standards.md` v2 (minimalist) 준수** — 5초/30초 룰, 정점 ≤ 10, 간선 ≤ 8, callout ≤ 1, 80% 회색/흰색 + 강조 색 1~2 정도.
>
> 작성 도구: **draw.io** (`.drawio` 파일, 저장 경로 `raw/diagrams/keycloak-patterns/`). Mermaid `graph TD` 는 시스템 아키텍처용으로 사용 금지 — Mermaid 는 시퀀스/ER 다이어그램 전용.
>
> **공통 시각 어휘** (모든 6 패턴 공통):
> - **주황 box + 주황 굵은 화살** = 그 패턴의 주인공 (Edge proxy, tunnel, brokering 등)
> - **파란 box + 파란 굵은 화살** = SPA-direct OIDC 또는 Keycloak brokering 핵심 경로
> - **흰색 box + 회색 가는 화살** = 보조 컴포넌트 / 부차 경로
> - **회색 점선 box** = External system (Google OIDC 등)
> - **빨간 callout** = 그 패턴의 가장 큰 보안/운영 함정 (정확히 1개)
> - **③, ④ 같은 번호** = 시각적 흐름 순서. 본문이 같은 번호로 받아 설명함.
### 3-1-1. P1A — Edge ForwardAuth (no Google)
![[raw/diagrams/keycloak-patterns/architecture-p1a-edge-no-google-2026-05-26.drawio]]
**다이어그램이 답하는 질문**: edge proxy가 인증 게이트일 때, 백엔드는 어떻게 인증 코드 0줄로 동작하는가?
**4단계 흐름** (다이어그램 ①~④):
1. `① HTTPS` (강조 — 진입 경로) — User browser 가 Edge zone 의 oauth2-proxy 에 요청
2. `② OIDC redirect` — proxy 가 미인증 요청을 Keycloak 으로 redirect (Authorization Code + PKCE)
3. `③ 로그인 + token` — Keycloak 로그인 UI 후 token 발급 (점선 = 사용자 매개 redirect)
4. `④ X-Forwarded-User` (강조 — 핵심 위탁) — proxy 가 인증 사용자명을 헤더로 backend 에 전달
**핵심 함정** (헤더 spoofing):
- backend 가 `X-Forwarded-User` 헤더만으로 사용자 식별 → ingress 우회 경로 존재 시 위조 가능
- 해결: NetworkPolicy (k8s) 또는 SG (AWS) 로 proxy → backend 만 통과시키고, 가능하면 mTLS 추가
**컴포넌트 책임 (P1A)**:
| 컴포넌트 | 역할 | 스택 |
|---|---|---|
| Edge Proxy | OIDC 인증 게이트 — 인증 안 된 요청을 Keycloak으로 redirect | oauth2-proxy 또는 Traefik ForwardAuth |
| Backend API | 비즈니스 로직만. 인증 검증은 proxy에 위임. 헤더로 사용자 식별 | Spring Boot 3.x (Spring Security 미사용) |
| Keycloak | Authorization Server. 사용자 DB + OIDC discovery | Keycloak 25.x + PostgreSQL 16 |
**P1A 트레이드오프**:
- 장점: backend 가 인증 코드 0줄. 다국적 polyglot 백엔드에 균일하게 인증 적용 용이.
- 단점: backend 가 헤더 신뢰 모델 → 네트워크 격리 실패 시 전면 우회.
**출처 (Sources)**:
- oauth2-proxy ForwardAuth — [[raw/branch-notes/feature-keycloak-edge-forwardauth-no-google]]
- Traefik ForwardAuth — [[raw/branch-notes/feature-keycloak-edge-forwardauth-no-google]]
### 3-1-2. P1B — Edge ForwardAuth + Google federation
![[raw/diagrams/keycloak-patterns/architecture-p1b-edge-google-2026-05-26.drawio]]
**다이어그램이 답하는 질문**: P1A 에 Google 로그인을 붙이면 Keycloak IdP brokering 만으로 SPA/Backend 변경 없이 가능한가?
**5단계 흐름** (다이어그램 ①~⑤):
1. `① HTTPS` — User → oauth2-proxy
2. `② OIDC redirect` — proxy → Keycloak (OIDC AS)
3. `③ Google 로그인` (강조 — 외부 IdP 위탁 핵심) — Keycloak → Google OIDC
4. `④ id_token (email_verified)` (점선 = 외부 호출) — Google → Keycloak, First Broker Login Flow 진입
5. `⑤ X-Forwarded-User` — proxy → backend (P1A 와 동일)
**핵심 함정** (First Broker Login Flow — email-match auto-linking):
- Keycloak 기본 옵션이 email 기반 자동 linking 제공
- Google 이 `email_verified=false` 인 사용자도 통과시키면 본인 외 사용자의 기존 계정 탈취 가능
- 해결: First Broker Login Flow 에서 `Confirm Link Existing Account` 강제 + `email_verified=true` 필수
**P1A 대비 추가/변화**:
- Keycloak ← Google IdP brokering 설정 추가
- Backend / proxy 코드는 변경 0줄 — Keycloak Realm 설정만 추가
**P1B 트레이드오프**:
- 장점: SPA/Backend 코드 변경 0줄로 Google SSO 추가
- 단점: First Broker Login Flow 설정 실수 시 계정 탈취 위험. Google API 의존성 운영 부담.
**출처 (Sources)**:
- Keycloak IdP brokering — [[raw/branch-notes/feature-keycloak-edge-forwardauth-google-federation]]
- First Broker Login Flow 보안 — [[raw/branch-notes/feature-keycloak-edge-forwardauth-google-federation]]
### 3-1-3. P2A — Cluster-internal SPA-direct (no Google)
![[raw/diagrams/keycloak-patterns/architecture-p2a-cluster-internal-no-google-2026-05-26.drawio]]
**다이어그램이 답하는 질문**: Edge proxy 없이 SPA 가 직접 OIDC 할 때, Backend 는 어떻게 JWT 신뢰를 닫는가?
**4단계 흐름** (다이어그램 ①~④):
1. `① HTTPS GET (SPA)` — User → nginx 가 호스팅하는 vanilla JS SPA 자원 수령
2. `② OIDC + PKCE` (강조 — SPA 가 토큰 보유) — SPA → Keycloak, 직접 token 흐름 (P1 과의 결정적 차이)
3. `③ Bearer access_token` (강조 — API 호출) — SPA → Backend, `Authorization: Bearer ...`
4. `④ JWKS` (강조 — 신뢰 닫기) — Backend → Keycloak 에서 검증 공개키 조회
**핵심 함정** (XSS surface):
- SPA 가 access/refresh token 을 브라우저 메모리/스토리지에 보유 → XSS 1건 = 세션 전체 탈취
- 해결: refresh token 보호가 필요하면 BFF(P1) 로 전환, 또는 httpOnly cookie 전략 검토
**P1A 대비 차이**:
- SPA 가 토큰 직접 보유 → XSS surface ↑, BFF 패턴 검토 가치 있음
- Backend 가 JWT validator 코드 보유 (`iss`, `aud`, `exp`, signature) — Spring Security 6.x Resource Server
**P2A 트레이드오프**:
- 장점: 컴포넌트 단순 (proxy 1개 제거). frontend 가 OIDC 흐름 완전 제어 가능.
- 단점: XSS surface 확대 + backend 가 JWT 검증 코드 보유 → polyglot 백엔드 마다 구현 필요.
**출처 (Sources)**:
- Spring Security Resource Server — [[raw/branch-notes/feature-keycloak-internal-spa-direct-no-google]]
- PKCE 흐름 (RFC 7636) — [[raw/branch-notes/feature-keycloak-internal-spa-direct-no-google]]
### 3-1-4. P2B — Cluster-internal + Google federation
![[raw/diagrams/keycloak-patterns/architecture-p2b-cluster-internal-google-2026-05-26.drawio]]
**다이어그램이 답하는 질문**: P2A 에 Google brokering 을 추가할 때, SPA/Backend 코드는 그대로 둘 수 있는가?
**5단계 흐름** (다이어그램 ①~⑤):
1. `① HTTPS GET` — User → nginx SPA
2. `② OIDC + PKCE` — SPA → Keycloak (P2A 와 동일)
3. `③ Google 로그인` (강조 — 외부 IdP 위탁) — Keycloak → Google
4. `④ id_token` (점선 = 외부 호출) — Google → Keycloak, First Broker Login Flow
5. `⑤ Bearer + JWKS` — SPA → Backend (Bearer), Backend → Keycloak (JWKS)
**핵심 함정** (P1B + P2A 중첩):
- P1B 의 email-match auto-linking + P2A 의 SPA XSS surface 가 모두 적용됨
- 해결: `Confirm Link Existing Account` 강제 + `email_verified=true` + 클라이언트 CSP / sanitize 강화 / 필요 시 BFF(P1) 로 이주
**P2A 대비 추가/변화**:
- Keycloak Realm 에 Google IdP 등록만 추가 (SPA/Backend 변경 0줄)
- First Broker Login Flow 보안 옵션 추가 검토 필요
**P2B 트레이드오프**:
- 장점: SPA/Backend 코드 0줄 변경으로 Google SSO 추가
- 단점: 두 함정 (auto-linking + XSS) 가 중첩되어 보안 운영 부담 ↑
**출처 (Sources)**:
- Keycloak IdP brokering — [[raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation]]
- First Broker Login Flow 보안 — [[raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation]]
### 3-1-5. P3A — Single EC2 (no Google) — **실 구현 대상**
![[raw/diagrams/keycloak-patterns/architecture-p3a-single-ec2-no-google-2026-05-26.drawio]]
**다이어그램이 답하는 질문**: 사용자 요청이 어떤 컴포넌트를 어떤 순서로 거치며 인증되는가?
**5단계 흐름** (다이어그램 ①~⑤):
1. `① HTTPS GET /` — User browser 가 nginx 에서 SPA 정적 자원 받음
2. `② OIDC + PKCE` (강조 — 핵심 경로) — Browser 가 Keycloak 으로 직접 redirect, Authorization Code + PKCE 흐름
3. `③ Bearer token + /api` — SPA 가 받은 access_token 으로 API 호출
4. `④ proxy_pass` — nginx 가 Spring Boot 로 reverse proxy
5. `⑤ JWKS` (강조 — 검증 경로) — Spring Boot 가 Keycloak 에서 JWT 검증 키 조회
**핵심 함정** (`KC_HOSTNAME`):
- Browser 는 EC2 public hostname 으로 Keycloak 호출 → JWT 의 `iss` claim = public host
- Backend 는 `localhost:8180` 로 JWKS 조회 → `iss` 비교 시 mismatch → 401
- 해결: docker-compose 에 `KC_HOSTNAME=<public-host>` + `KC_HTTP_ENABLED=true` 명시
**부차 함정** (`redirect_uri`):
- Keycloak client 의 Valid Redirect URIs 등록 시 `localhost` 만 등록 / browser 가 `127.0.0.1` 접근 → mismatch
- 해결: 등록과 접근 hostname 1:1 일치 또는 둘 다 등록
**P3A 트레이드오프**:
- 장점: 학습 / 개발 환경 최단 셋업. 단일 docker-compose 로 끝남.
- 단점: SPoF — EC2 1대 다운 = 전체 정지. 운영급은 P2A + Keycloak HA cluster.
**출처 (Sources)**:
- KC_HOSTNAME 함정 — [[raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch]]
- redirect_uri 함정 — [[raw/branch-notes/feature-keycloak-docker-compose-stack]]
- OIDC PKCE — [[raw/official-docs/oauth2-pkce-rfc-7636]] (또는 해당 official-doc)
**다이어그램 편집**: Obsidian draw.io 플러그인으로 위 임베드 더블클릭. 또는 [draw.io 데스크탑 앱](https://www.drawio.com/) 사용.
### 3-1-6. P3B — Single EC2 + Google federation
![[raw/diagrams/keycloak-patterns/architecture-p3b-single-ec2-google-2026-05-26.drawio]]
**다이어그램이 답하는 질문**: P3A 에 Google 을 붙이려면 왜 외부 HTTPS endpoint(tunnel/RP) 가 강제되는가?
**6단계 흐름** (다이어그램 ①~⑥):
1. `① HTTPS` (강조 — 공개 진입) — User → HTTPS tunnel (cloudflared / ngrok / Caddy)
2. `② localhost (HTTP)` (강조 — tunnel 가 localhost 위탁) — tunnel → nginx
3. `③ proxy_pass /api` — nginx → Spring Boot Backend
4. `④ JWKS` — Backend → Keycloak 검증 키 조회
5. `⑤ Google 로그인 (공개 HTTPS)` (강조 — 외부 IdP) — Keycloak → Google
6. `⑥ id_token` (점선 = 외부 호출) — Google → Keycloak
**핵심 함정** (`KC_HOSTNAME` 공개 hostname 강제):
- Google 이 검증하는 `redirect_uri` 와 Keycloak issuer 가 모두 공개 HTTPS host 여야 함
- P3A 처럼 `localhost` 로 설정 시 Google 흐름 실패 또는 issuer 불일치 401
- 해결: `KC_HOSTNAME=<public-host>` + Keycloak Realm Client 의 `Valid Redirect URIs` 를 public URL 로
**P3A 대비 추가 요구사항**: EC2 를 외부 HTTPS 로 노출 (Google 이 redirect_uri 검증). cloudflared / ngrok / 정식 도메인 + Caddy 중 택일.
**P3B 트레이드오프**:
- 장점: P3A 단순성을 유지하면서 Google SSO 추가 가능
- 단점: tunnel/RP 운영 부담 + KC_HOSTNAME 설정 함정 (P3A 의 함정이 hostname 만 바뀌어 재발)
**출처 (Sources)**:
- KC_HOSTNAME 함정 — [[raw/branch-notes/feature-keycloak-single-ec2-google-federation]]
- cloudflared / ngrok 비교 — [[raw/branch-notes/feature-keycloak-single-ec2-google-federation]]
### 3-1-7. AP2 (Token-Mediating) · AP3 (BFF) — needs-diagram
> 신 primary 축의 AP2·AP3 은 기존 6 `.drawio`(P1A~P3B = AP1 배포 변형 + AP4)에 대응 아키텍처 다이어그램이 없다. **needs-diagram** — 사용자가 draw.io 로 작성 후 아래 백틱을 풀어 활성 임베드로 전환한다. (미작성 파일을 활성 임베드로 두면 9a 린터가 `BROKEN_LINK` 로 잡으므로 placeholder 는 백틱 코드로 비활성.)
- AP2 Token-Mediating Backend: `![[raw/diagrams/keycloak-patterns/architecture-ap2-token-mediating-2026-07-14.drawio.svg]]`
- AP3 Backend-for-Frontend: `![[raw/diagrams/keycloak-patterns/architecture-ap3-bff-2026-07-14.drawio.svg]]`
작성 시 `rules/diagram-standards.md` v2 (minimalist) 준수 + `wiki-diagram-reviewer` ≥95 별도 확인(게이트는 존재만 판정, 품질 ≥95 는 사용자가 별도 실행).
<!-- section-id: sequence -->
## 3-2. 핵심 시퀀스 (Key Sequences — Mermaid)
> 토큰 교환 흐름은 패턴마다 다름. happy path + 주요 error path 함께. `templates/project-template.md` §4 표준 준수 — `autonumber`, `actor` vs `participant` 구분, alt/opt/loop 블록, `Note over` 비자명한 동작.
<!-- section-id: runtime-flow -->
### P3A: vanilla JS + PKCE + Keycloak (단일 EC2, no Google)
```mermaid
sequenceDiagram
autonumber
actor User
participant SPA as vanilla JS SPA (nginx)
participant KC as Keycloak (Authorization Server)
participant API as Spring Boot Resource Server
User->>SPA: 로그인 클릭
SPA->>SPA: PKCE code_verifier 생성, code_challenge=SHA256(verifier)
SPA->>KC: GET /realms/<realm>/protocol/openid-connect/auth?client_id=<spa>&response_type=code&code_challenge=...&redirect_uri=...
KC-->>User: 로그인 폼 redirect
User->>KC: id/password 입력
alt 자격 증명 유효
KC-->>SPA: 302 redirect with authorization code
SPA->>KC: POST /token (code + code_verifier)
KC-->>SPA: 200 OK {access_token, id_token, refresh_token}
SPA->>API: GET /api/v1/<resource> + Authorization: Bearer <access_token>
API->>API: JWT 검증 (iss, aud, exp, signature with JWKS)
alt JWT 유효
API-->>SPA: 200 OK {resource}
SPA-->>User: 화면 표시
else aud claim mismatch
API-->>SPA: 401 Unauthorized {error: invalid_token}
SPA-->>User: 에러 + 재로그인 유도
end
else 자격 증명 무효
KC-->>SPA: 302 redirect with error=access_denied
SPA-->>User: 에러 표시
end
```
> 위 P3A 시퀀스 = **AP1**(SPA-direct + Resource Server)의 single-EC2 배포. 아래는 나머지 3 패턴의 핵심 시퀀스(happy + error path).
### AP2: Token-Mediating Backend (백엔드 confidential client, access token 만 브라우저 전달)
```mermaid
sequenceDiagram
autonumber
actor User
participant B as Browser (SPA)
participant BE as Backend (confidential client)
participant KC as Keycloak
participant API as Resource API
User->>B: 로그인 클릭
B->>BE: GET /login
BE->>KC: Authorization Code (confidential client + secret)
KC-->>User: 로그인 폼
User->>KC: 자격 증명
alt 로그인 성공
KC-->>BE: access_token + refresh_token
Note over BE: refresh_token 은 백엔드만 보유 (브라우저 미전달)
BE-->>B: access_token 만 전달
B->>API: GET /resource + Bearer access_token
API-->>B: 200 OK
else access_token 만료 (재발급은 백엔드 경유)
API-->>B: 401 invalid_token
B->>BE: POST /token/refresh
BE->>KC: refresh_grant (백엔드 보유 refresh_token)
KC-->>BE: 새 access_token
BE-->>B: 새 access_token
end
```
### AP3: Backend-for-Frontend (BFF) — 토큰 0개, session cookie 만
```mermaid
sequenceDiagram
autonumber
actor User
participant B as Browser (SPA)
participant BFF as BFF (Spring oauth2Login)
participant KC as Keycloak
participant API as Resource API
User->>B: 로그인 클릭
B->>BFF: GET /oauth2/authorization/keycloak
BFF->>KC: Authorization Code (confidential client)
KC-->>User: 로그인 폼
User->>KC: 자격 증명
alt 로그인 성공
KC-->>BFF: 302 + authorization code
BFF->>KC: POST /token (code + client_secret)
KC-->>BFF: access/refresh token (BFF session 에 저장)
BFF-->>B: Set-Cookie: SESSION (httpOnly) — 브라우저에 토큰 없음
B->>BFF: GET /api/resource (cookie 자동 첨부)
BFF->>API: GET /resource + Bearer (BFF 가 토큰 부착)
API-->>BFF: 200 OK
BFF-->>B: 200 OK
else CSRF (cookie 자동첨부 악용)
Note over B,BFF: 외부 사이트가 cookie 실린 상태변경 요청 위조
BFF-->>B: 403 (SameSite=Lax + CSRF token 검증 실패로 차단)
end
```
### Gateway forward-auth (oauth2-proxy, 백엔드 인증코드 0줄)
```mermaid
sequenceDiagram
autonumber
actor User
participant Proxy as oauth2-proxy (ForwardAuth)
participant KC as Keycloak
participant API as Backend API
User->>Proxy: GET /app (미인증)
Proxy->>KC: OIDC redirect (Authorization Code + PKCE)
KC-->>User: 로그인 폼
User->>KC: 자격 증명
alt 인증 성공
KC-->>Proxy: token (proxy session 보관)
Proxy->>API: GET /app + X-Forwarded-User: <sub>
API-->>Proxy: 200 OK (헤더만으로 사용자 식별)
Proxy-->>User: 200 OK
else 헤더 위조 우회 시도 (signature 함정)
Note over API: ingress 우회 경로로 X-Forwarded-User 직접 주입
API-->>User: 200 (❌ network 격리 실패 시 위조 성공)
Note over API: 방어 = NetworkPolicy/SG 로 proxy→API 만 통과 (+ mTLS)
end
```
### P2B → AP4/AP1 + Google: Google IdP federation 추가 흐름 (sub-branch에 상세)
(Google brokering 은 AP1~AP4 어디에도 코드 0줄로 얹히는 cross-cutting. 상세 다이어그램은 각 sub-branch — [[raw/branch-notes/feature-keycloak-edge-forwardauth-google-federation]], [[raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation]] — 에서.)
## 4. 인프라 / 기술 스택
| 영역 | 선택 |
|------|------|
| 언어 | Java 21 (Backend), JavaScript ES2022+ (vanilla, no framework) |
| 프레임워크 | Spring Boot 3.x + Spring Security 6.x (Resource Server) |
| Authorization Server | Keycloak 25.x (latest stable as of 2026-05) |
| DB (Keycloak) | PostgreSQL 16 |
| Web Server (SPA) | nginx (static file serving) |
| 컨테이너 | Docker + Docker Compose |
| 배포 환경 (P3A 한정) | 단일 EC2 (학습용) — HTTPS termination 선택적 |
| OIDC client library | 직접 PKCE 구현 또는 `oidc-client-ts` |
<!-- section-id: implementation-boundaries -->
## 5. 작업 범위 (Project-level Scope)
### 포함 범위 (2026-07-14 교정 — 4 인증-아키텍처 패턴 실 구현)
- **4 인증-아키텍처 패턴(AP1~AP4) 실 구현** — 모두 single-EC2 로컬 스택 위에서 E2E(`locally-verified`) + signature 함정 재현→해결 (§1 성공기준, §8.0 branch 분해).
- **Google IdP brokering** 을 cross-cutting 변형으로 1회 실 구현(어느 패턴에 얹어도 SPA/Backend 코드 0줄 변경 검증 포함).
- 4 패턴 통합 trade-off 매트릭스 (토큰 위치 / 검증 주체 / XSS·CSRF surface / 선택 기준 / keycloak 설정).
- 각 패턴의 공식 문서 · 기술블로그 출처 raw 보존 + 채택/대안/비교 구조 명시.
- 각 패턴에서 토큰 종류의 교환 시점 · 저장 위치 · 만료 정책 정리.
### 제외 범위
- **배포 토폴로지 별도 구현**: 인증 아키텍처는 single-EC2 로 실 구현하고, cluster-internal / edge 는 hostname·issuer·network 차이만 문서화(별도 k8s/Traefik 환경 구축 안 함 — §2.2 cross-cutting).
- 다른 OIDC Provider(GitHub / Auth0 / Cognito) federation. Google만.
- React/Vue 등 SPA 프레임워크 (vanilla JS 유지).
- 모바일 / 네이티브 앱 흐름 (PKCE for native).
- mTLS, FAPI(Financial-grade API), DPoP 등 고급 보안 옵션.
### Deferred — keycloak SERVER-side 심화 트랙 (2026-07-14 명시, 지금 안 함)
> 사용자 확정: 4 client-integration 패턴 E2E 를 먼저 끝낸 뒤 별도 학습 트랙으로 착수. "keycloak 다 알기" 의 나머지 절반(server/운영 측면)이며, **본 프로젝트 현 phase 의 out-of-scope 이되 폐기가 아니라 후속 트랙으로 예약**한다.
- **SPI (Service Provider Interface)** — custom authenticator / mapper / event listener 작성.
- **HA cluster** — Infinispan 분산 캐시, active-active Keycloak 다중 노드.
- **multi-realm / 멀티테넌시** — realm-per-tenant vs client-per-tenant.
- **Admin REST API 자동화** — realm/client export·import 를 코드로 (`feature-keycloak-realm-client-export` 씨앗 존재).
- **LDAP / user federation** — 외부 사용자 저장소 연동.
- **token revocation 심화** — JWT stateless 한계 + blacklist / introspection endpoint.
**인접 관심사 커버리지 note (9-coverage — silent 누락 방지):**
- **인가(Authorization) — keycloak roles → Spring `@PreAuthorize`**: 본 4 패턴은 authN(인증) 토큰 흐름에 집중한다. RBAC 인가는 별개 관심사이며 기존 씨앗 `feature-keycloak-idp-mappers-claim-to-role` 존재 — 4 패턴 E2E 후 각 패턴에 얹음(현 phase 명시적 out-of-scope, 폐기 아님).
- **Logout / session termination (front/back-channel)**: AP3 BFF session 종료 · AP1 토큰 만료로 부분 커버. refresh rotation + logout 후 session·token 무효화는 `WI-KEYCLOAK-PATTERNS-OVERVIEW-007`(`feature-keycloak-refresh-rotation-and-logout`, §8.0 registry)이 **현 phase** 로 커버한다. **통합 front/back-channel logout 흐름 심화**만 deferred (2026-07-23 경계 명확화 — §8.0 WI-007 과의 이중 서술 해소).
## 기술 결정
> **Legacy reference (v1).** 아래 비교표는 rationale과 대안을 보존한다. stable decision owner와 branch 상속 기준은 §6.1 registry다.
> 프로젝트 차원 기술 결정. 각 결정은 검토한 대안 + 외부근거 wikilink 필수 (근거 없으면 `UNSUPPORTED_DECISION`). 결정별 *깊은* 대안 비교는 branch 단계(`/branch-spec` + `wiki-decision-researcher`)로 위임 — 본 표는 hub 차원 stack/축 결정의 근거 소싱까지.
| 결정 영역 | 선택 | 검토한 대안 | 채택 이유 | 트레이드오프 | 근거 자료 |
|---|---|---|---|---|---|
| **분류 primary 축** | 인증 통합 아키텍처 4패턴 (AP1~AP4) | (a) 배포×federation 6패턴(기존) (b) IETF 3패턴만 (c) 멘토 "4패턴" | 6축은 auth 아키텍처 2종만 exercise + AP2/AP3 누락. IETF 3 + edge-proxy = 4 가 keycloak client 통합 canonical 축이며 중복(P2≡P3, B=A+federation) 제거 + 누락(AP2·AP3) 채움 | edge-proxy(AP4)는 IETF 3종 밖 실무 확장 — 표준 인용은 IETF 3까지만 유효 | [[raw/official-docs/oauth2-browser-based-apps-ietf-draft]], [[raw/company-tech-blogs/curity-bff-pattern-spa]] |
| **AP1 SPA client 유형** | public client + Authorization Code + PKCE | implicit flow / password grant | implicit·password 는 OAuth 2.1 에서 사실상 배제. PKCE 가 public client 표준 | 토큰이 브라우저에 노출(XSS surface) — AP2/AP3 로 완화 가능 | [[raw/official-docs/oauth2-pkce-rfc-7636]], [[raw/official-docs/oauth-v2-1-draft-ietf]] |
| **AP3 BFF 토큰 위치** | 백엔드 session (브라우저 = cookie 만) | 브라우저 저장 (localStorage / memory) | "토큰을 브라우저 밖에 두는 것이 XSS 로부터 보호하는 유일한 방법"(Curity) — 최고 보안강도 | stateful(session store 필요), 모바일 별도 흐름, CSRF surface 증가 | [[raw/company-tech-blogs/curity-bff-pattern-spa]], [[raw/official-docs/oauth2-browser-based-apps-ietf-draft]] |
| **AP2 Token-Mediating Backend** | 백엔드 confidential client 가 토큰 획득, access token 만 브라우저 전달 | AP1(전부 브라우저) / AP3(전부 백엔드) | BFF 보다 경량(모든 요청 proxy 불필요) + AP1 보다 refresh token 보호 | access token 은 여전히 브라우저 노출 | [[raw/official-docs/oauth2-browser-based-apps-ietf-draft]] |
| **AP4 Edge forward-auth** | oauth2-proxy ForwardAuth | Traefik ForwardAuth / nginx `auth_request` / Spring Cloud Gateway TokenRelay | 백엔드 인증코드 0줄, polyglot 균일 적용 | 헤더 신뢰 모델 → network 격리 실패 시 전면 우회 | [[raw/official-docs/oauth2-proxy-keycloak-oidc-provider-official]], [[raw/official-docs/oauth2-proxy-nginx-integration-official]] |
| **Google federation** | Keycloak IdP brokering + First Broker Login hardening | SPA/Backend 가 직접 Google OIDC 호출 | keycloak 이 brokering 흡수 → 앱 코드 0줄. First Broker Login 으로 account linking 제어 | email auto-linking 계정탈취 위험 → Confirm Link Existing Account 필수 | [[raw/official-docs/keycloak-identity-brokering-overview-official]], [[raw/official-docs/keycloak-first-broker-login-flow]] |
> 소싱 bound(회당 6): 위 6개로 마감. 추가 결정(HTTPS termination D2~D5 등)은 §13 에 기존 근거 보존, deferred server-side 트랙 결정은 후속 `/project-spec` 회차로 이월(`deferred`).
## 프로젝트 레벨 고정 결정 (Fixed Decisions — branch 간 충돌 방지)
> **Legacy reference (v1).** F1~F5의 현재 stable owner는 아래 §6.1 registry다. F1은 taxonomy decision에 병합하며 중복 owner row를 만들지 않는다.
> 여러 branch 가 공유하므로 hub 가 1회 고정. branch 는 재정의 금지, 본 절을 참조만 (SSOT).
| # | 고정 결정 | SSOT 위치 | 이유 / 충돌 방지 |
|---|---|---|---|
| F1 | **인증 패턴 taxonomy = §2 (AP1~AP4 + cross-cutting)** | §2 (본 노트) | 모든 branch 는 §2 의 AP-ID 를 인용. 패턴을 branch 에서 재정의하면 6-vs-4 혼선 재발 |
| F2 | **done-bar = E2E + signature 함정 재현→해결** | §1 성공기준 | 4 패턴 branch 가 동일 완료 기준 상속. 등급은 `src/` 검증 후 `locally-verified` |
| F3 | **단일 공유 realm `keycloak-patterns`, 패턴당 client 1개** (spa-public / token-mediating-confidential / bff-confidential / edge-proxy) | §4 스택 + baseline branch | client 분리로 `aud` claim 충돌 방지. AP1 audience validator 가 client별 aud 검증 가능 |
| F4 | **confidential client secret = env var, 미커밋** | baseline branch | AP2·AP3 는 client secret 보유. `.env`/`KC_*` 로 주입, realm export JSON 에 평문 금지 |
| F5 | **E2E 실 구현 배포 = single-EC2 docker-compose** (cluster-internal/edge 는 문서만) | §2.2, §5 | 배포 토폴로지는 cross-cutting 이라 auth 아키텍처를 바꾸지 않음 — 실 구현 1벌로 4 패턴 모두 검증 |
<!-- section-id: project-decisions -->
## 6.1 안정 결정 레지스트리
| Decision ID | Revision | Domain | Decision Summary | Status | Owner | Evidence |
|---|---:|---|---|---|---|---|
| `DEC-KEYCLOAK-PATTERNS-OVERVIEW-AUTH-TAXONOMY-001` | 1 | `auth-taxonomy` | canonical 분류축은 AP1~AP4 인증 통합 아키텍처와 cross-cutting 변형이다 | `active` | [[raw/project-notes/keycloak-patterns-overview]] | §2; §기술 결정 `분류 primary 축`; F1 |
| `DEC-KEYCLOAK-PATTERNS-OVERVIEW-SPA-CLIENT-001` | 1 | `spa-client` | AP1은 public client와 Authorization Code + PKCE를 사용한다 | `active` | [[raw/project-notes/keycloak-patterns-overview]] | §기술 결정 `AP1 SPA client 유형`; [[raw/official-docs/oauth2-pkce-rfc-7636]] |
| `DEC-KEYCLOAK-PATTERNS-OVERVIEW-BFF-SESSION-001` | 1 | `bff-session` | AP3는 token을 backend session에 두고 browser에는 cookie만 둔다 | `active` | [[raw/project-notes/keycloak-patterns-overview]] | §기술 결정 `AP3 BFF 토큰 위치`; [[raw/official-docs/oauth2-browser-based-apps-ietf-draft]] |
| `DEC-KEYCLOAK-PATTERNS-OVERVIEW-TOKEN-MEDIATING-001` | 1 | `token-mediating` | AP2 backend가 token을 획득하고 access token만 browser에 전달한다 | `active` | [[raw/project-notes/keycloak-patterns-overview]] | §기술 결정 `AP2 Token-Mediating Backend`; [[raw/official-docs/oauth2-browser-based-apps-ietf-draft]] |
| `DEC-KEYCLOAK-PATTERNS-OVERVIEW-EDGE-FORWARDAUTH-001` | 1 | `edge-forwardauth` | AP4는 oauth2-proxy ForwardAuth를 사용한다 | `active` | [[raw/project-notes/keycloak-patterns-overview]] | §기술 결정 `AP4 Edge forward-auth`; [[raw/official-docs/oauth2-proxy-keycloak-oidc-provider-official]] |
| `DEC-KEYCLOAK-PATTERNS-OVERVIEW-IDP-BROKERING-001` | 1 | `idp-brokering` | Google federation은 Keycloak IdP brokering과 hardened First Broker Login을 사용한다 | `active` | [[raw/project-notes/keycloak-patterns-overview]] | §기술 결정 `Google federation`; [[raw/official-docs/keycloak-first-broker-login-flow]] |
| `DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001` | 1 | `acceptance` | done-bar는 E2E success와 signature security failure 재현·해결 evidence다 | `documented-only` | [[raw/project-notes/keycloak-patterns-overview]] | F2; §1 성공 기준 |
| `DEC-KEYCLOAK-PATTERNS-OVERVIEW-REALM-CLIENT-001` | 1 | `realm-client` | 단일 realm keycloak-patterns에서 인증 패턴별 client를 분리한다 | `documented-only` | [[raw/project-notes/keycloak-patterns-overview]] | F3; §4 stack |
| `DEC-KEYCLOAK-PATTERNS-OVERVIEW-SECRET-BOUNDARY-001` | 1 | `secret-boundary` | confidential client secret은 env var로 주입하고 commit·realm export 평문을 금지한다 | `documented-only` | [[raw/project-notes/keycloak-patterns-overview]] | F4 |
| `DEC-KEYCLOAK-PATTERNS-OVERVIEW-DEPLOYMENT-001` | 1 | `deployment` | AP1~AP4 E2E 구현 topology는 single-EC2 docker-compose다 | `documented-only` | [[raw/project-notes/keycloak-patterns-overview]] | F5; §2.2; §5 |
<!-- section-id: project-work-items -->
## 8.0 실행계획
| Work Item ID | branch slug | 완료 조건 (측정가능) | Applies Decisions | Dependencies | Status |
|---|---|---|---|---|---|
| `WI-KEYCLOAK-PATTERNS-OVERVIEW-001` | `feature-keycloak-docker-compose-stack` | Keycloak·PostgreSQL·nginx·Spring 4 containers가 healthy이고 admin console에 접속된다 | `DEC-KEYCLOAK-PATTERNS-OVERVIEW-DEPLOYMENT-001@1` | - | `planned` |
| `WI-KEYCLOAK-PATTERNS-OVERVIEW-002` | `feature-keycloak-realm-client-export` | realm import와 client 4개 등록 후 unauthenticated protected endpoint가 401이고 export가 재현된다 | `DEC-KEYCLOAK-PATTERNS-OVERVIEW-REALM-CLIENT-001@1`, `DEC-KEYCLOAK-PATTERNS-OVERVIEW-SECRET-BOUNDARY-001@1` | `WI-KEYCLOAK-PATTERNS-OVERVIEW-001` | `planned` |
| `WI-KEYCLOAK-PATTERNS-OVERVIEW-003` | `feature-keycloak-vanilla-js-spa-pkce` | vanilla JS PKCE login·token 수령·protected API 200이 재현된다 | `DEC-KEYCLOAK-PATTERNS-OVERVIEW-SPA-CLIENT-001@1`, `DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1` | `WI-KEYCLOAK-PATTERNS-OVERVIEW-002` | `planned` |
| `WI-KEYCLOAK-PATTERNS-OVERVIEW-004` | `feature-keycloak-spring-rs-audience-validator` | foreign audience token 수용 실패를 재현하고 validator 적용 후 401을 검증한다 | `DEC-KEYCLOAK-PATTERNS-OVERVIEW-SPA-CLIENT-001@1`, `DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1` | `WI-KEYCLOAK-PATTERNS-OVERVIEW-003` | `planned` |
| `WI-KEYCLOAK-PATTERNS-OVERVIEW-005` | `feature-keycloak-iss-claim-hostname-mismatch` | hostname 미설정 iss mismatch 401과 설정 후 복구 log가 존재한다 | `DEC-KEYCLOAK-PATTERNS-OVERVIEW-SPA-CLIENT-001@1`, `DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1` | `WI-KEYCLOAK-PATTERNS-OVERVIEW-003` | `planned` |
| `WI-KEYCLOAK-PATTERNS-OVERVIEW-006` | `feature-keycloak-spa-token-storage-tradeoff` | 저장 위치별 browser token read/XSS surface가 재현되고 선택이 기록된다 | `DEC-KEYCLOAK-PATTERNS-OVERVIEW-SPA-CLIENT-001@1` | `WI-KEYCLOAK-PATTERNS-OVERVIEW-003` | `planned` |
| `WI-KEYCLOAK-PATTERNS-OVERVIEW-007` | `feature-keycloak-refresh-rotation-and-logout` | refresh rotation과 logout 후 session·token 무효화가 검증된다 | `DEC-KEYCLOAK-PATTERNS-OVERVIEW-SPA-CLIENT-001@1`, `DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1` | `WI-KEYCLOAK-PATTERNS-OVERVIEW-004` | `planned` |
| `WI-KEYCLOAK-PATTERNS-OVERVIEW-008` | `feature-keycloak-token-mediating-confidential-client` | confidential backend의 code-token 교환과 server-side refresh 보관이 검증된다 | `DEC-KEYCLOAK-PATTERNS-OVERVIEW-TOKEN-MEDIATING-001@1`, `DEC-KEYCLOAK-PATTERNS-OVERVIEW-SECRET-BOUNDARY-001@1` | `WI-KEYCLOAK-PATTERNS-OVERVIEW-002` | `in-progress` |
| `WI-KEYCLOAK-PATTERNS-OVERVIEW-009` | `feature-keycloak-token-mediating-access-handoff` | browser가 access token으로 API 200을 받고 refresh token은 network response에 존재하지 않는다 | `DEC-KEYCLOAK-PATTERNS-OVERVIEW-TOKEN-MEDIATING-001@1`, `DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1` | `WI-KEYCLOAK-PATTERNS-OVERVIEW-008` | `in-progress` |
| `WI-KEYCLOAK-PATTERNS-OVERVIEW-010` | `feature-keycloak-bff-oauth2login-session` | browser token 0개와 SESSION cookie만으로 BFF proxy API 200이 재현된다 | `DEC-KEYCLOAK-PATTERNS-OVERVIEW-BFF-SESSION-001@1`, `DEC-KEYCLOAK-PATTERNS-OVERVIEW-SECRET-BOUNDARY-001@1` | `WI-KEYCLOAK-PATTERNS-OVERVIEW-002` | `in-progress` |
| `WI-KEYCLOAK-PATTERNS-OVERVIEW-011` | `feature-keycloak-bff-csrf-samesite-defense` | CSRF를 재현하고 SameSite와 CSRF token 적용 후 403을 검증한다 | `DEC-KEYCLOAK-PATTERNS-OVERVIEW-BFF-SESSION-001@1`, `DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1` | `WI-KEYCLOAK-PATTERNS-OVERVIEW-010` | `in-progress` |
| `WI-KEYCLOAK-PATTERNS-OVERVIEW-012` | `feature-keycloak-oauth2-proxy-oidc-flow` | unauthenticated redirect와 login 후 X-Forwarded-User backend 200이 재현된다 | `DEC-KEYCLOAK-PATTERNS-OVERVIEW-EDGE-FORWARDAUTH-001@1`, `DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1` | `WI-KEYCLOAK-PATTERNS-OVERVIEW-002` | `planned` |
| `WI-KEYCLOAK-PATTERNS-OVERVIEW-013` | `feature-keycloak-nginx-auth-request-integration` | nginx auth_request 통합과 4KB cookie split case가 검증된다 | `DEC-KEYCLOAK-PATTERNS-OVERVIEW-EDGE-FORWARDAUTH-001@1` | `WI-KEYCLOAK-PATTERNS-OVERVIEW-012` | `planned` |
| `WI-KEYCLOAK-PATTERNS-OVERVIEW-014` | `feature-keycloak-header-spoofing-defense` | forwarded-user spoofing 우회를 재현하고 network isolation 후 차단한다 | `DEC-KEYCLOAK-PATTERNS-OVERVIEW-EDGE-FORWARDAUTH-001@1`, `DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1` | `WI-KEYCLOAK-PATTERNS-OVERVIEW-012` | `planned` |
| `WI-KEYCLOAK-PATTERNS-OVERVIEW-015` | `feature-keycloak-idp-brokering-google-client` | Google login·Keycloak user mapping 200과 application diff 0줄이 검증된다 | `DEC-KEYCLOAK-PATTERNS-OVERVIEW-IDP-BROKERING-001@1` | `WI-KEYCLOAK-PATTERNS-OVERVIEW-003` | `planned` |
| `WI-KEYCLOAK-PATTERNS-OVERVIEW-016` | `feature-keycloak-first-broker-login-flow` | unsafe auto-linking을 재현하고 Confirm Link Existing Account로 차단한다 | `DEC-KEYCLOAK-PATTERNS-OVERVIEW-IDP-BROKERING-001@1`, `DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1` | `WI-KEYCLOAK-PATTERNS-OVERVIEW-015` | `planned` |
| `WI-KEYCLOAK-PATTERNS-OVERVIEW-017` | `feature-keycloak-google-claim-attribute-mapping` | Google email·name claim이 Keycloak attribute로 매핑된다 | `DEC-KEYCLOAK-PATTERNS-OVERVIEW-IDP-BROKERING-001@1` | `WI-KEYCLOAK-PATTERNS-OVERVIEW-015` | `planned` |
| `WI-KEYCLOAK-PATTERNS-OVERVIEW-018` | `feature-keycloak-account-linking-sub-vs-email` | sub와 email linking key의 security comparison과 선택이 기록된다 | `DEC-KEYCLOAK-PATTERNS-OVERVIEW-IDP-BROKERING-001@1` | `WI-KEYCLOAK-PATTERNS-OVERVIEW-016` | `planned` |
| `WI-KEYCLOAK-PATTERNS-OVERVIEW-019` | `feature-keycloak-four-pattern-tradeoff-matrix` | 4패턴 비교표의 모든 cell이 구현 WI evidence를 가리킨다 | `DEC-KEYCLOAK-PATTERNS-OVERVIEW-AUTH-TAXONOMY-001@1`, `DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1` | `WI-KEYCLOAK-PATTERNS-OVERVIEW-003`, `WI-KEYCLOAK-PATTERNS-OVERVIEW-008`, `WI-KEYCLOAK-PATTERNS-OVERVIEW-010`, `WI-KEYCLOAK-PATTERNS-OVERVIEW-012` | `in-progress` |
| `WI-KEYCLOAK-PATTERNS-OVERVIEW-020` | `feature-keycloak-patterns` | project governance hub가 AP1~AP4 taxonomy와 child progress index를 유지한다 | `DEC-KEYCLOAK-PATTERNS-OVERVIEW-AUTH-TAXONOMY-001@1` | - | `in-progress` |
## 실행계획 (Branch decomposition, R4)
> **Legacy reference (v1).** 아래 2-tier grouping과 priority 설명은 보존한다. Tier-1은 파일이 아닌 group label이며 stable branch ID·decision pin·dependency의 SSOT는 위 Work Item Registry다.
> **`/project-spec` 핸드오프 섹션.** 2-tier — **Tier-1 = 패턴 parent**(project 직접 자식, `parent_branch:` 비어있음), **Tier-2 = 실제 "1 branch = 1 PR" 단위**(각 Tier-2 가 "1개로 끝낼 양"). 각 branch 의 *네이밍 + 측정가능 목표조건 + 우선순위 + 의존*만 적는다 — 결정 내용·메커니즘은 `/branch <slug>` 생성 후 `/branch-spec` 가 깊게 채운다. slug 는 `rules/naming-conventions.md` §2.1 준수(`feature-` + content-descriptive, numbered hierarchy 없음). 규모: **7 Tier-1 / 19 Tier-2 ≈ 19 PR** — "바로 끝낼 양" 아님(수 주 분량). 기존 27 sub-sub-branch 를 4-패턴으로 re-map + AP2 만 신규.
### Tier-1 개요 (7 parent)
| Tier-1 parent slug | 역할 | Tier-2 수 | 우선순위 |
|---|---|---|---|
| `feature-keycloak-local-stack-baseline` | 4 패턴 공유 로컬 스택 | 2 | P1 |
| `feature-keycloak-spa-direct-resource-server` | AP1 | 5 | P2 |
| `feature-keycloak-token-mediating-backend` | AP2 (신규) | 2 | P3 |
| `feature-keycloak-bff-session-proxy` | AP3 | 2 | P3 |
| `feature-keycloak-edge-forwardauth-proxy` | AP4 | 3 | P3 |
| `feature-keycloak-google-idp-brokering` | Google cross-cutting | 4 | P4 |
| `feature-keycloak-four-pattern-tradeoff-matrix` | 종합 매트릭스 | 1 | P5 |
> **명명 정합 (2026-07-14 감사 — 파일 ↔ hub 매칭 검증)**:
> - **Tier-2 실 구현 19개**: **14개 = 기존 branch-note 파일과 슬러그 정확히 일치 ✓**. 5개 = 신규 예정(`token-mediating-confidential-client`·`-access-handoff`, `bff-oauth2login-session`·`-csrf-samesite-defense`, `four-pattern-tradeoff-matrix`) → `/branch` 로 생성.
> - **Tier-1 그룹명 7개는 파일이 아니라 그룹 라벨**이다(파일로 만들면 기존 pattern 노트 §12.1 와 중복되므로 만들지 않음). 각 Tier-2 의 물리적 `parent_branch:` 는 현재 옛 pattern 노트(§12.1)를 가리키고, AP 그룹 소속은 **본 분해표가 SSOT**. 링크 깨짐 0.
> - **재-parent 매핑**(각 그룹 실 작업 착수 시 `wiki-doc-author mode=migrate` 로 반영 — 지금은 cosmetic 이라 미실행): Google Tier-2 4개(현 parent P1B `edge-forwardauth-google-federation`) → Google 그룹, `spring-rs-audience-validator`·`spa-token-storage-tradeoff`(현 parent P2A `internal-spa-direct-no-google`) → AP1 그룹. 나머지는 현 parent 가 이미 AP anchor(single-ec2/edge)와 정합.
### 그룹 0 — 공유 baseline (parent `feature-keycloak-local-stack-baseline`)
| Tier-2 sub-branch | 달성 목표 조건 (측정가능) | 우선순위 | 의존 |
|---|---|---|---|
| `feature-keycloak-docker-compose-stack` | `docker compose up` → Keycloak+PostgreSQL+nginx+Spring 4 컨테이너 healthy + KC admin 콘솔 접속 | P1 | - |
| `feature-keycloak-realm-client-export` | realm `keycloak-patterns` import + client 4개(spa-public / token-mediating-confidential / bff-confidential / edge-proxy) 등록 + 보호 endpoint 토큰없이 `401` + JSON export 재현 | P1 | `feature-keycloak-docker-compose-stack` |
### 그룹 1 — AP1 SPA-direct + Resource Server (parent `feature-keycloak-spa-direct-resource-server`)
| Tier-2 sub-branch | 달성 목표 조건 (측정가능) | 우선순위 | 의존 |
|---|---|---|---|
| `feature-keycloak-vanilla-js-spa-pkce` | vanilla JS 가 PKCE(code_verifier/challenge)로 로그인 → access_token 수령 → `/api` `200` | P2 | baseline |
| `feature-keycloak-spring-rs-audience-validator` | Spring RS 가 JWKS 검증 + `aud` 미검증 → 타 client 토큰 통과 재현 → audience validator 로 `401` | P2 | `feature-keycloak-vanilla-js-spa-pkce` |
| `feature-keycloak-iss-claim-hostname-mismatch` | `KC_HOSTNAME` 미설정 → `iss` mismatch `401` 재현 → 설정으로 해결(로그 before/after) | P2 | `feature-keycloak-vanilla-js-spa-pkce` |
| `feature-keycloak-spa-token-storage-tradeoff` | 저장위치별 XSS surface 시연(JS 에서 토큰 read 가능 재현) + 저장 전략 결정 기록 | P3 | `feature-keycloak-vanilla-js-spa-pkce` |
| `feature-keycloak-refresh-rotation-and-logout` | refresh rotation 동작 + 로그아웃 시 세션/토큰 무효화 확인 | P3 | `feature-keycloak-spring-rs-audience-validator` |
### 그룹 2 — AP2 Token-Mediating Backend (parent `feature-keycloak-token-mediating-backend`) — 신규
| Tier-2 sub-branch | 달성 목표 조건 (측정가능) | 우선순위 | 의존 |
|---|---|---|---|
| `feature-keycloak-token-mediating-confidential-client` | 백엔드(confidential client)가 code→token 교환 성공(client_secret) + refresh 를 서버 세션 보관 | P3 | baseline |
| `feature-keycloak-token-mediating-access-handoff` | access_token 만 브라우저 전달 → 브라우저가 RS 직접 호출 `200` + refresh 가 네트워크탭/응답 바디에 **부재** 확인 | P3 | `feature-keycloak-token-mediating-confidential-client` |
### 그룹 3 — AP3 BFF (parent `feature-keycloak-bff-session-proxy`)
| Tier-2 sub-branch | 달성 목표 조건 (측정가능) | 우선순위 | 의존 |
|---|---|---|---|
| `feature-keycloak-bff-oauth2login-session` | Spring `oauth2Login` 로그인 → 브라우저 토큰 0개(SESSION cookie 만) + BFF proxy 경유 API `200` | P3 | baseline |
| `feature-keycloak-bff-csrf-samesite-defense` | cookie 자동첨부 CSRF 재현 → SameSite + CSRF token 으로 `403` 차단 | P3 | `feature-keycloak-bff-oauth2login-session` |
### 그룹 4 — AP4 Edge forward-auth (parent `feature-keycloak-edge-forwardauth-proxy`)
| Tier-2 sub-branch | 달성 목표 조건 (측정가능) | 우선순위 | 의존 |
|---|---|---|---|
| `feature-keycloak-oauth2-proxy-oidc-flow` | oauth2-proxy 앞단 → 미인증 redirect → 인증 후 backend `X-Forwarded-User` `200` | P3 | baseline |
| `feature-keycloak-nginx-auth-request-integration` | nginx `auth_request` 통합 동작 + 4kb cookie 분할 함정 확인 | P3 | `feature-keycloak-oauth2-proxy-oidc-flow` |
| `feature-keycloak-header-spoofing-defense` | `X-Forwarded-User` 위조 우회 재현 → network 격리(SG/NetworkPolicy)로 차단 | P3 | `feature-keycloak-oauth2-proxy-oidc-flow` |
> (감사 정정 2026-07-14) `feature-keycloak-traefik-forwardauth-alternative` 은 impl branch 아님 — 본문이 스스로 "선택 기준 정리까지만" 이고 P3A 는 nginx+oauth2-proxy 채택. **FOLD-IN**(비교 근거)으로 강등, 아래 fold-in 목록 참조.
### 그룹 5 — Google IdP brokering cross-cutting (parent `feature-keycloak-google-idp-brokering`)
| Tier-2 sub-branch | 달성 목표 조건 (측정가능) | 우선순위 | 의존 |
|---|---|---|---|
| `feature-keycloak-idp-brokering-google-client` | realm 에 Google IdP 등록 → Google 계정 로그인 → Keycloak 사용자 매핑 `200` + SPA/Backend diff 0줄 검증 | P4 | AP1 group |
| `feature-keycloak-first-broker-login-flow` | `email_verified=false` auto-linking 계정탈취 재현 → Confirm Link Existing Account 로 차단 | P4 | `feature-keycloak-idp-brokering-google-client` |
| `feature-keycloak-google-claim-attribute-mapping` | Google claim → Keycloak attribute 매핑(email/name) 확인 | P4 | `feature-keycloak-idp-brokering-google-client` |
| `feature-keycloak-account-linking-sub-vs-email` | 계정 linking 키 `sub` vs `email` 보안 비교 → 결정 기록 | P4 | `feature-keycloak-first-broker-login-flow` |
### 그룹 6 — 종합 (parent 없음, 최종)
| Tier-2 sub-branch | 달성 목표 조건 (측정가능) | 우선순위 | 의존 |
|---|---|---|---|
| `feature-keycloak-four-pattern-tradeoff-matrix` | 4 패턴을 {토큰 위치 · 검증 주체 · XSS/CSRF surface · 선택 기준 · keycloak 설정} 열로 한 표에 정리 + 각 셀이 구현 branch 검증 증거 link | P5 | AP1~AP4 4개 group |
> **기존 sub-branch 흡수/승격**: 위 Tier-2 대부분은 기존 27 sub-sub-branch(§12.1 legacy)의 실 구현 승격이다. 예외 — 학습노트로 fold-in(별도 impl branch 아님): `feature-keycloak-pkce-flow-stages`·`feature-keycloak-spring-rs-role-mapping`·`feature-keycloak-refresh-token-rotation`(→ AP1 그룹 근거), `feature-keycloak-bff-vs-spa-direct`(→ AP3 비교 근거), `feature-keycloak-traefik-forwardauth-alternative`(→ AP4 비교 근거, P3A 는 nginx+oauth2-proxy 채택), `feature-keycloak-federation-spa-zero-change`·`feature-keycloak-three-leg-trust-chain`·`feature-keycloak-account-linking-spa-ux`(→ Google 그룹 근거). **AP2 그룹 2개(신규)만 완전 신규 파일.** 실제 파일 rename/re-parent 는 `wiki-doc-author mode=migrate` 로 점진(자동 mv 금지).
>
> **배포 토폴로지 sub-branch 는 documentation-only**(F5): `feature-keycloak-public-domain-tunneling`·`feature-keycloak-reverse-proxy-headers`·`feature-keycloak-https-termination-caddy-nginx`·`feature-keycloak-google-redirect-uri-policy` 는 single-EC2 실 구현 밖 배포 변형이라 §13 HTTPS termination 근거로 문서만 유지(별도 impl branch 아님). **인가(RBAC)** `feature-keycloak-idp-mappers-claim-to-role` 는 §5 deferred(authZ)로 이월.
>
> **중복 정합 완료 (2026-07-14 감사 — 각 파일에 정합 노트 삽입)**: (1) `spring-rs-role-mapping` ↔ `spring-rs-audience-validator` — Spring RS 셋업·`aud` 검증은 **audience-validator 가 owner**, role-mapping 의 role→RBAC 부분만 deferred authZ. (2) `idp-mappers-claim-to-role` ↔ `google-claim-attribute-mapping` — attribute-mapping 은 **google-claim-attribute-mapping 이 owner**, idp-mappers 의 claim→role 부분만 deferred authZ. (확인된 비-중복: account-linking sub-vs-email↔spa-ux, federation-spa-zero-change↔three-leg-trust-chain, refresh 2개 — 상호보완이라 유지.)
## 6. 본인이 한 작업 (사실만)
각 항목 옆에 증거 등급 표기:
가능한 등급: `actually-implemented` | `locally-verified` | `prod-verified` | `documented-only` | `planned` | `needs-confirmation`
- 6 패턴 분류 축 정의 (배치 × federation) — 등급: `documented-only` *(2026-07-14 인증 아키텍처 4패턴으로 축 교정됨 — 아래 참조)*
- 6 sub-branch 작성 (목표 / 다이어그램 / 토큰 sequence / 장단점) — 등급: `documented-only`
- 28 raw 외부 자료 보존 (공식 문서 + 기술블로그) — 등급: `documented-only`
- **분류축 교정 + hub §2 재편** (2026-07-14): 배치×federation 6패턴 → 인증 아키텍처 4패턴(AP1~AP4) primary + cross-cutting. 측정가능 성공기준(§1)·기술결정 소싱표(6/6)·Branch 분해표(7 branch) 추가. IETF browser-based-apps raw 보존 — 등급: `documented-only`
- AP1~AP4 실 구현 (코드) — 등급: `planned` (Branch 분해표 Phase 2~3)
## 7. 마주친 문제 / 트러블슈팅
> Phase 1(문서화) 단계에서 발견한 함정. Phase 2(P3A 실 구현) 시 마주칠 가능성 높음.
- **iss claim mismatch (단일 EC2)**:
- 원인: Keycloak `KC_HOSTNAME` 미설정 시 browser와 backend가 다른 hostname을 보고, JWT `iss` claim이 mismatch → backend JWT validation 실패.
- 해결: `KC_HOSTNAME=<hostname>` + `KC_HTTP_ENABLED=true` 명시. browser/backend 모두 같은 issuer 사용.
- **Spring Security `aud` claim 미검증 (default)**:
- 원인: Spring Security 기본 JWT validator는 `iss`, `exp`만 검증, `aud` 검증 안 함. 다른 client용 토큰이 본 backend로 흘러들 위험.
- 해결: custom `OAuth2TokenValidator<Jwt>``aud=<expected-client-id>` 검증 추가.
- **redirect_uri mismatch (localhost vs 127.0.0.1)**:
- 원인: Keycloak client 설정의 `Valid Redirect URIs``localhost`만 등록했는데 browser가 `127.0.0.1`로 접근 (또는 반대).
- 해결: 등록과 사용 hostname을 1:1 일치시키거나 둘 다 등록.
- **Google First Broker Login Flow의 email-match auto-linking 보안 위험**:
- 원인: 기본 First Broker Login Flow가 email 기반 자동 linking 옵션 제공. 그러나 Google이 email_verified=false 인 사용자 통과 가능 → 본인 외 사용자의 기존 계정 탈취 가능.
- 해결: First Broker Login Flow에 "Confirm Link Existing Account" + email_verified=true 강제 + manual confirm.
## 8. 자신 없는 부분
> 면접에서 받을 가능성이 있지만 본인이 확실히 답할 수 없는 영역. P3A 구현 + Phase 3 sub-sub-branch 학습 후 보강 예정.
- BFF (Backend-for-Frontend) 패턴 실 구현 경험 부재 — 문서만 봤음
- Keycloak SPI (Service Provider Interface)로 custom IdP 작성
- 운영 환경에서 token revocation 처리 (JWT 자체는 stateless, blacklist 필요 시)
- Keycloak multi-realm 운영 (테넌트별 realm 분리 vs 단일 realm + client별 분리)
- HTTPS termination 위치 (nginx vs Caddy vs ALB) trade-off
- Keycloak 자체의 HA 구성 (Infinispan + cluster)
## 9. 관련 자료
- 저장소 URL: `/home/donghyeon/workspace/keycloak-patterns/` (별도 git repo, **아직 비어 있음** — Phase 2 진입 시 생성)
- 관련 PR / 커밋: 없음
- canonical SSOT (본 문서): [[raw/project-notes/keycloak-patterns-overview]]
## 10. 진행 단계 (Phase)
| Phase | 내용 | 상태 |
|-------|------|------|
| **Phase 0** | 배치×federation 6패턴 정의 + 34 branch-note + 6 `.drawio` + 외부자료 보존 | ✅ 2026-05-27 완료 |
| **Phase 1** | **분류축 교정**: 인증 아키텍처 4패턴(AP1~AP4) 재편 + 측정가능 성공기준(§1) + 기술결정 소싱(6/6) + Branch 분해표(7 branch) | ✅ 2026-07-14 완료 |
| **Phase 2** | `feature-keycloak-local-stack-baseline` + AP1~AP4 E2E + signature 함정 재현 (Branch 분해표 P1~P3) | ⏳ Pending |
| **Phase 3** | Google IdP brokering cross-cutting + 4패턴 trade-off 매트릭스 (Branch 분해표 P4~P5) | ⏳ Pending |
| **Phase 4** | AP1~AP4 `locally-verified` 승급 + `wiki/projects/keycloak-patterns/` 추출 | ⏳ Pending |
| **Phase 5** (deferred) | keycloak server-side 심화 트랙 (SPI / HA / multi-realm / LDAP — §5 Deferred) | ⏳ Deferred |
## 11. wiki 추출 정책
- **Phase 4 완료 시점에 추출**: P3A의 `actually-implemented` / `locally-verified` 항목만 `wiki/projects/keycloak-patterns/`로 추출.
- **추출하지 않음**: P1A/P1B/P2A/P2B/P3B는 `documented-only` 유지, wiki/projects 승급 안 함. 단, 학습 노트 가치가 있으면 별도 `wiki/concepts/keycloak-deployment-patterns.md`로 합성 검토 (Phase 4 이후).
## 12. 묶음 (이 프로젝트에 묶이는 모든 raw 자료)
<!-- GENERATED: sources:start -->
- [[raw/company-tech-blogs/curity-bff-pattern-spa]]
- [[raw/company-tech-blogs/keycloak-google-login-codemancers]]
- [[raw/company-tech-blogs/keycloak-jwt-role-extraction-betweendata]]
- [[raw/official-docs/aws-alb-target-security-group-restriction-official]]
- [[raw/official-docs/aws-cloudfront-origin-shared-secret-header-official]]
- [[raw/official-docs/aws-security-group-referencing-official]]
- [[raw/official-docs/chrome-third-party-cookie-policy-google-official]]
- [[raw/official-docs/cloudflare-tunnel-routing-official]]
- [[raw/official-docs/docker-compose-depends-on-healthcheck]]
- [[raw/official-docs/docker-compose-networking-extra-hosts-official]]
- [[raw/official-docs/docker-engine-20-10-release-notes-official]]
- [[raw/official-docs/docker-host-network-driver-official]]
- [[raw/official-docs/docker-port-publishing-loopback-bind-official]]
- [[raw/official-docs/google-oauth-app-verification-state-overview-official]]
- [[raw/official-docs/google-oauth-manage-app-audience-official]]
- [[raw/official-docs/google-oauth2-client-application-types-official]]
- [[raw/official-docs/google-oauth2-policies-environment-separation-official]]
- [[raw/official-docs/google-oauth2-redirect-uri-validation-official]]
- [[raw/official-docs/google-oauth2-web-server-flow-official]]
- [[raw/official-docs/google-oidc-discovery-spec]]
- [[raw/official-docs/google-openid-connect-oidc]]
- [[raw/official-docs/istio-mtls-cert-rotation-official]]
- [[raw/official-docs/k8s-network-policy-official]]
- [[raw/official-docs/keycloak-2500-hostname-v2-release-official]]
- [[raw/official-docs/keycloak-2600-hostname-v1-removed-official]]
- [[raw/official-docs/keycloak-account-console-unlink-lockout-guard-official]]
- [[raw/official-docs/keycloak-client-initiated-account-linking]]
- [[raw/official-docs/keycloak-client-pkce-method-enforcement-official]]
- [[raw/official-docs/keycloak-configuring-database]]
- [[raw/official-docs/keycloak-first-broker-login-flow]]
- [[raw/official-docs/keycloak-first-broker-login-verify-authenticators-official]]
- [[raw/official-docs/keycloak-first-login-flow]]
- [[raw/official-docs/keycloak-getting-started-docker]]
- [[raw/official-docs/keycloak-google-idp-setup]]
- [[raw/official-docs/keycloak-health-checks]]
- [[raw/official-docs/keycloak-hostname-configuration]]
- [[raw/official-docs/keycloak-identity-broker-spi]]
- [[raw/official-docs/keycloak-identity-brokering-overview-official]]
- [[raw/official-docs/keycloak-identity-provider-mappers]]
- [[raw/official-docs/keycloak-identity-provider-redirector-default-idp-official]]
- [[raw/official-docs/keycloak-identity-provider-sync-mode-official]]
- [[raw/official-docs/keycloak-identity-provider-trust-email-official]]
- [[raw/official-docs/keycloak-idp-hide-on-login-page-toggle-official]]
- [[raw/official-docs/keycloak-idp-hint-client-suggested-official]]
- [[raw/official-docs/keycloak-import-export-realms]]
- [[raw/official-docs/keycloak-oidc-logout-endpoint-official]]
- [[raw/official-docs/keycloak-refresh-token-rotation-reuse-admin-official]]
- [[raw/official-docs/keycloak-refresh-token-rotation-sessions-official]]
- [[raw/official-docs/keycloak-reverseproxy-official]]
- [[raw/official-docs/keycloak-securing-apps-overview-official]]
- [[raw/official-docs/keycloak-server-containers-docker]]
- [[raw/official-docs/nginx-auth-request-module-official]]
- [[raw/official-docs/nginx-core-module-location-internal-official]]
- [[raw/official-docs/ngrok-http-tunnel-official]]
- [[raw/official-docs/oauth-v2-1-draft-ietf]]
- [[raw/official-docs/oauth2-browser-based-apps-ietf-draft]]
- [[raw/official-docs/oauth2-pkce-rfc-7636]]
- [[raw/official-docs/oauth2-proxy-behaviour-cookie-vs-bearer-official]]
- [[raw/official-docs/oauth2-proxy-cookie-redirect-flags-official]]
- [[raw/official-docs/oauth2-proxy-endpoints-official]]
- [[raw/official-docs/oauth2-proxy-endpoints-signout-official]]
- [[raw/official-docs/oauth2-proxy-keycloak-oidc-provider-official]]
- [[raw/official-docs/oauth2-proxy-nginx-integration-official]]
- [[raw/official-docs/oauth2-proxy-overview-config-official]]
- [[raw/official-docs/oauth2-proxy-session-storage-official]]
- [[raw/official-docs/oauth2-token-revocation-rfc-7009]]
- [[raw/official-docs/oidc-client-ts-library]]
- [[raw/official-docs/openid-connect-core-id-token-validation]]
- [[raw/official-docs/owasp-html5-storage-xss-spa]]
- [[raw/official-docs/proxy-pass-request-body-nginx-official]]
- [[raw/official-docs/security-jwt-rfc-7519-validation]]
- [[raw/official-docs/silent-check-sso-third-party-cookies-keycloak-official]]
- [[raw/official-docs/spring-security-authorization-defense-in-depth]]
- [[raw/official-docs/spring-security-authorize-http-requests]]
- [[raw/official-docs/spring-security-method-security]]
- [[raw/official-docs/spring-security-nested-authorities-claim-issue-15201]]
- [[raw/official-docs/spring-security-resource-server-jwt]]
- [[raw/official-docs/third-party-cookie-blocking-safari-webkit-official]]
- [[raw/official-docs/traefik-forwardauth-middleware-official]]
- [[raw/official-docs/traefik-hub-oidc-middleware-official]]
- [[raw/official-docs/traefik-oidc-community-plugin-lukaszraczylo-official]]
<!-- GENERATED: sources:end -->
> 본 project-note는 cluster의 entry point. 모든 branch / sources / errors / interviews / lectures 가 여기로 upward link. hub 측에서도 카테고리별 명시.
### 12.1 브랜치
<!-- GENERATED: branches:start -->
- [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]]
- [[raw/branch-notes/feature-keycloak-bff-csrf-samesite-defense]]
- [[raw/branch-notes/feature-keycloak-bff-oauth2login-session]]
- [[raw/branch-notes/feature-keycloak-docker-compose-stack]]
- [[raw/branch-notes/feature-keycloak-first-broker-login-flow]]
- [[raw/branch-notes/feature-keycloak-four-pattern-tradeoff-matrix]]
- [[raw/branch-notes/feature-keycloak-google-claim-attribute-mapping]]
- [[raw/branch-notes/feature-keycloak-header-spoofing-defense]]
- [[raw/branch-notes/feature-keycloak-idp-brokering-google-client]]
- [[raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch]]
- [[raw/branch-notes/feature-keycloak-nginx-auth-request-integration]]
- [[raw/branch-notes/feature-keycloak-oauth2-proxy-oidc-flow]]
- [[raw/branch-notes/feature-keycloak-patterns]]
- [[raw/branch-notes/feature-keycloak-realm-client-export]]
- [[raw/branch-notes/feature-keycloak-refresh-rotation-and-logout]]
- [[raw/branch-notes/feature-keycloak-spa-token-storage-tradeoff]]
- [[raw/branch-notes/feature-keycloak-spring-rs-audience-validator]]
- [[raw/branch-notes/feature-keycloak-token-mediating-access-handoff]]
- [[raw/branch-notes/feature-keycloak-token-mediating-confidential-client]]
- [[raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce]]
<!-- GENERATED: branches:end -->
> generated reverse view는 child branch의 v2 contract migration 후 채운다. 아래 수기 legacy inventory는 그 전까지 navigation으로 보존한다.
> ⚠️ **Legacy inventory (구 6패턴 축).** 아래 목록은 Phase 0 의 배치×federation 구조다. **현 실행계획은 "Branch 분해 / 실행계획 (R4)" 표** — 아래 branch 들은 §2.3 매핑대로 AP1~AP4 로 re-map/승격 대상(실제 rename 은 `wiki-doc-author mode=migrate`). 신규 작업 진입점은 분해표를 따른다.
> Root branch + 6개 Tier-2 sub-branches + 27개 Tier-3 sub-sub-branches.
- **Root**: [[raw/branch-notes/feature-keycloak-patterns]] — 전체 진행 인덱스 hub
- **Tier-2 sub-branches** (구 6 패턴 → §2.3 매핑: P1x→AP4, P2x/P3x→AP1):
- [[raw/branch-notes/feature-keycloak-edge-forwardauth-no-google]] — P1A Edge / Ingress (no Google)
- [[raw/branch-notes/feature-keycloak-edge-forwardauth-google-federation]] — P1B Edge / Ingress + Google federation
- [[raw/branch-notes/feature-keycloak-internal-spa-direct-no-google]] — P2A Cluster-internal SPA-direct (no Google)
- [[raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation]] — P2B Cluster-internal + Google federation
- [[raw/branch-notes/feature-keycloak-single-ec2-no-google]] — P3A Single EC2 (no Google) — **실 구현 대상**
- [[raw/branch-notes/feature-keycloak-single-ec2-google-federation]] — P3B Single EC2 + Google federation
- **Tier-3 sub-sub-branches** (각 패턴 4~6개): root branch [[raw/branch-notes/feature-keycloak-patterns]] 의 Cluster 섹션 참조.
### 12.2 근거 자료 (프로젝트 전체 차원 foundational 조사)
- 개별 official-doc / company-tech-blog 들은 각 sub-branch 의 Sources 표에서 cited.
- [[raw/official-docs/oauth2-browser-based-apps-ietf-draft]] — IETF draft-ietf-oauth-browser-based-apps-27. **4-패턴 인증 아키텍처 taxonomy(§2.1)** 가 준거로 삼는 업계 표준 3대 아키텍처(BFF / Token-Mediating Backend / Browser-based OAuth Client, decreasing order of security) 정의의 foundational 근거. AP4(edge forward-auth)만 IETF 3종 밖 실무 확장.
### 12.3 오류 기록 (branch 외 발생한 환경·운영 이슈)
- (없음 — Phase 3 P3A 구현 진입 시 발생 예상)
### 12.4 면접 준비
- (없음 — sub-branch 별로 면접 후보 누적 후 별도 raw/interviews/ 신설 예정)
### 12.5 강의
- (없음 — 필요 시 Keycloak Summit / OIDC 강의 추가)
### 12.6 파생 wiki 문서
- canonical 검증 사실: (없음 — Phase 4 시 `wiki/projects/keycloak-patterns/` 신설)
- 관련 일반 개념: (없음 — Phase 4 이후 `wiki/concepts/keycloak-deployment-patterns.md` 검토)
- 포트폴리오: (없음)
- 블로그 글: (없음)
## 13. Phase 5 Additional Evidence Raws (2026-05-27)
> Phase 5B 외부 근거 추가 보강. HTTPS termination 결정 (P3B 의 tunnel/RP 선택, §3-1-6 의 cloudflared / ngrok / Caddy 비교) 영역에 5개 신규 raw 파일 (`raw/official-docs/` 하위) 추가. 각 raw 는 frontmatter `related_projects: [keycloak-patterns]` 보유.
>
> **출처 신뢰도 (CLAUDE.md §5 정합)**: 모두 `source_type: official-doc` (IETF RFC, OWASP cheat sheet, vendor 공식 reference). company-tech-blog 없음.
>
> **사용 경계**: 본 섹션은 raw evidence 의 cluster-level index. 각 raw 의 정확한 Claim ID / Usage Boundary 는 raw 파일 자체의 `## Claims Extracted` 섹션 참조. 본 project-note 는 owning sub-branch 에 매핑할 뿐, raw 의 verbatim claim 을 그대로 keycloak best practice 로 단정하지 않음.
### 13.1 HTTPS Termination / TLS Policy (5 raw)
P3B (Single EC2 + Google federation) 의 HTTPS termination 결정 — Google IdP 가 검증하는 `redirect_uri` 와 Keycloak issuer 가 모두 공개 HTTPS host 여야 함 (§3-1-6 핵심 함정 `KC_HOSTNAME`). 아래 5개 raw 가 termination 전략 선택지 (D2~D5) 의 외부 근거.
| raw | 채택 위치 (decision / sub-branch) | 사용 근거 |
| --- | --- | --- |
| [[raw/official-docs/rfc8996-tls10-tls11-deprecation]] | https-termination D5 (TLS 버전 policy), [[raw/branch-notes/feature-keycloak-single-ec2-google-federation]] | RFC 8996 (TLS 1.0/1.1 Deprecation, IETF 2021) 이 P3B 의 HTTPS termination (tunnel/RP 어느 쪽이든) 이 최소 TLS 1.2+ 강제 해야 하는 baseline. Google OIDC discovery endpoint 도 TLS 1.2+ 요구. |
| [[raw/official-docs/owasp-hsts-cheat-sheet]] | https-termination D5 (HSTS header policy), [[raw/branch-notes/feature-keycloak-single-ec2-google-federation]] | OWASP HSTS Cheat Sheet 가 `Strict-Transport-Security` header 의 baseline (`max-age`, `includeSubDomains`, `preload`). Caddy / Certbot+nginx / Cloudflare tunnel 어느 termination 도 HSTS 활성화 해야 함. preload 진입 결정은 branch-note 에서 별도 trade-off. |
| [[raw/official-docs/caddy-automatic-https-docs]] | https-termination D2 (Caddy option), [[raw/branch-notes/feature-keycloak-single-ec2-google-federation]] | Caddy 공식 "Automatic HTTPS" doc. P3B termination 선택지 중 정식 도메인 + Caddy 옵션 — Caddy 가 ACME (Let's Encrypt / ZeroSSL) 자동 발급·갱신·OCSP stapling 을 기본 제공. 트레이드오프: 단일 binary, config 간결성 vs nginx 운영 표준성. |
| [[raw/official-docs/certbot-user-guide]] | https-termination D3 (Certbot + nginx option), [[raw/branch-notes/feature-keycloak-single-ec2-google-federation]] | Certbot 공식 user guide. P3B termination 선택지 중 정식 도메인 + nginx + Certbot 옵션 — Certbot 이 Let's Encrypt ACME 클라이언트의 reference 구현. cron/systemd timer 기반 갱신, nginx plugin 의 in-place reload. 트레이드오프: 운영 표준성 (nginx) vs config 분리도 (Caddy 대비). |
| [[raw/official-docs/aws-acm-managed-renewal]] | https-termination D4 (AWS ALB/CloudFront option), [[raw/branch-notes/feature-keycloak-single-ec2-google-federation]] | AWS ACM Managed Renewal 공식 reference. P3B termination 선택지 중 AWS ALB / CloudFront 앞단 옵션 — ACM 이 publicly trusted cert 의 13개월 자동 갱신을 platform-side 에서 책임. EC2 내부 (Keycloak) 는 HTTP 또는 self-signed 로 충분. 트레이드오프: AWS lock-in vs 운영 부담 zero. |
**비교 매트릭스** (4개 termination option):
| 옵션 | cert 발급 자동화 | 인프라 위치 | lock-in | P3B 적합도 |
|---|---|---|---|---|
| cloudflared tunnel | Cloudflare 측 | 외부 (no inbound) | Cloudflare | 학습/dev 최적 (가장 가벼움) |
| Caddy + 도메인 | Caddy 자체 (ACME) | EC2 내 | none (open source) | 단일 binary, prod 가능 |
| nginx + Certbot + 도메인 | Certbot (cron) | EC2 내 | none | 운영 표준 (가장 친숙) |
| AWS ALB/CloudFront + ACM | ACM 자동 | AWS platform | AWS | prod 권장 (운영 부담 최소) |
**Out of scope** (P3B termination 선택 후 별도 분기): mTLS termination, FAPI 준수 termination, EV cert, multi-domain SAN, custom CA. ngrok 은 학습용 short-lived tunnel 로 cloudflared 대안 (별도 raw 미수집).
---
## 14. 아키텍처 검토 체크리스트 (작성/갱신 시 self-check)
- [x] 한 줄 요약 + 현재 상태 + 나의 역할 채워짐 (§1)
- [x] 측정 가능한 성공 기준 1개 이상 — **§1 성공 기준(측정가능, R1) 추가 (2026-07-14)**: 4 패턴 각각 E2E `200` + signature 함정 재현→해결, done-bar 정량화 완료. ✓
- [x] 아키텍처 다이어그램 1개 이상 첨부 (§3-1) — 기존 6 `.drawio` (2026-05-26, minimalist) ✓. **단 신 축 AP2·AP3 은 needs-diagram (§3-1-7) — 사용자 작성 대기.**
- [x] 다이어그램의 모든 컴포넌트가 라벨 + 역할 표기 ✓
- [x] 다이어그램의 모든 화살표가 프로토콜·데이터 종류 라벨링 ✓
- [x] 외부 시스템이 점선 + 회색으로 시각적 구분 ✓ (Google OIDC = dashed gray box)
- [x] 범례(Legend) 다이어그램 내부 + §3-1 도입부에 포함 ✓
- [x] 신뢰 경계 / 네트워크 경계 표시 ✓ (Edge zone / Internal / EC2 / Public HTTPS)
- [x] 시퀀스 다이어그램 1개 이상 (Mermaid) — happy path + error path 함께 (§3-2 P3A) ✓
- [x] Cluster 섹션의 root branch 목록 채워짐 (§12.1) ✓
- [x] 마지막 architecture review 날짜 frontmatter `architecture_review:` 에 기록 — 2026-05-26 ✓