937 lines
76 KiB
Markdown
937 lines
76 KiB
Markdown
---
|
||
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 호출용. 짧은 만료 (5–15분)
|
||
- `refresh token`: access token 갱신용. 긴 만료 (1–30일)
|
||
- `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 ✓
|