--- 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-.md`: 6 패턴별 sub-branch (예: `feature-keycloak-edge-forwardauth-no-google`) > - `raw/branch-notes/feature-keycloak-.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 호출용. 짧은 만료 (5–15분) - `refresh token`: access token 갱신용. 긴 만료 (1–30일) - `ID token` (JWT): 사용자 식별 정보. 백엔드는 보통 사용 안 함, 클라이언트가 사용자 표시용으로 사용. ## 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=` + `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=` + 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 는 사용자가 별도 실행). ## 3-2. 핵심 시퀀스 (Key Sequences — Mermaid) > 토큰 교환 흐름은 패턴마다 다름. happy path + 주요 error path 함께. `templates/project-template.md` §4 표준 준수 — `autonumber`, `actor` vs `participant` 구분, alt/opt/loop 블록, `Note over` 비자명한 동작. ### 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//protocol/openid-connect/auth?client_id=&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/ + Authorization: Bearer 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: 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` | ## 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 패턴 모두 검증 | ## 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 | ## 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` | `planned` | | `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` | `planned` | | `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 ` 생성 후 `/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=` + `KC_HTTP_ENABLED=true` 명시. browser/backend 모두 같은 issuer 사용. - **Spring Security `aud` claim 미검증 (default)**: - 원인: Spring Security 기본 JWT validator는 `iss`, `exp`만 검증, `aud` 검증 안 함. 다른 client용 토큰이 본 backend로 흘러들 위험. - 해결: custom `OAuth2TokenValidator`로 `aud=` 검증 추가. - **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 자료) - [[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]] > 본 project-note는 cluster의 entry point. 모든 branch / sources / errors / interviews / lectures 가 여기로 upward link. hub 측에서도 카테고리별 명시. ### 12.1 브랜치 - [[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-vanilla-js-spa-pkce]] > 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 ✓