400 lines
33 KiB
Markdown
400 lines
33 KiB
Markdown
---
|
|
title: branch / feature-keycloak-single-ec2-google-federation (P3B Single EC2 + Google federation)
|
|
source_type: branch-note
|
|
status: raw
|
|
id: BR-KEYCLOAK-CHILD-D5D01846
|
|
kind: branch-child
|
|
project: keycloak-patterns-overview
|
|
work_item: WI-KEYCLOAK-PATTERNS-OVERVIEW-020
|
|
inherits: [DEC-KEYCLOAK-PATTERNS-OVERVIEW-AUTH-TAXONOMY-001@1]
|
|
refines: []
|
|
overrides: []
|
|
depends_on: []
|
|
contract_packet: 1
|
|
branch: feature-keycloak-single-ec2-google-federation
|
|
parent_branch: feature-keycloak-patterns
|
|
related_projects: [keycloak-patterns]
|
|
tags: [branch, keycloak-patterns, auth, oauth2, oidc, docker]
|
|
created: 2026-05-25
|
|
target_merge:
|
|
status_label: in-progress
|
|
contract_packet_sha256: d7981aec13ea69dbc518a68fa2709ba3fa9a7f9ccc7a849a7b039926a9e82f3c
|
|
---
|
|
|
|
# branch: feature-keycloak-single-ec2-google-federation (P3B Single EC2 + Google federation)
|
|
|
|
> Layer: `raw/branch-notes/` — Keycloak 패턴 **P3B** 한정 sub-branch. **단일 EC2**(P3A 토폴로지) + **Google IdP brokering**. SPA / Spring Boot / Keycloak이 한 호스트에 동거하면서 Keycloak이 Google을 외부 IdP로 위임. SPA flow는 P3A와 동일 (Keycloak만 호출).
|
|
> 본 sub-branch는 **문서 + 다이어그램까지만**. 실제 EC2 + Google client 등록 + cloudflared / ngrok 시도는 P3A 완료 후의 선택적 확장.
|
|
> `status_label`: `in-progress`
|
|
|
|
<!-- section-id: branch-parent -->
|
|
## 부모 (필수)
|
|
|
|
[[raw/branch-notes/feature-keycloak-patterns]]
|
|
|
|
<!-- GENERATED: branch-contract:start -->
|
|
<!-- section-id: branch-contract-packet -->
|
|
## 브랜치 계약 패킷
|
|
|
|
- **생성 시 프로젝트 개정**: `1`
|
|
- **패킷 스키마**: `contract_packet: 1`
|
|
- **완료 조건**: project governance hub가 AP1~AP4 taxonomy와 child progress index를 유지한다
|
|
|
|
<!-- section-id: inherited-project-decisions -->
|
|
### 상속한 프로젝트 결정
|
|
|
|
| Decision Ref | Project Summary | Branch Application | Source |
|
|
|---|---|---|---|
|
|
| `DEC-KEYCLOAK-PATTERNS-OVERVIEW-AUTH-TAXONOMY-001@1` | canonical 분류축은 AP1~AP4 인증 통합 아키텍처와 cross-cutting 변형이다 | single-EC2와 Google federation을 cross-cutting 변형으로 분류한다 | [[raw/project-notes/keycloak-patterns-overview]] |
|
|
|
|
<!-- section-id: branch-local-decisions -->
|
|
### 브랜치 지역 결정
|
|
|
|
> 기존 branch-local 결정은 아래 `## Decision Evidence Map`의 D-row가 소유하며 이 packet에서 복제하지 않는다.
|
|
|
|
| Decision ID | Decision | Relation | Supporting Claims | Status |
|
|
|---|---|---|---|---|
|
|
|
|
<!-- section-id: declared-overrides -->
|
|
### 선언한 예외
|
|
|
|
| Override ID | Overrides | Reason | Approval | Status |
|
|
|---|---|---|---|---|
|
|
|
|
없음.
|
|
<!-- GENERATED: branch-contract:end -->
|
|
|
|
<!-- section-id: branch-goal -->
|
|
## 목표
|
|
|
|
P3A(단일 EC2, no Google)에 Google federation을 더했을 때 토큰 흐름이 어떻게 바뀌는지, 그리고 **단일 EC2 + 외부 IdP 조합이 만들어내는 새로운 제약**이 무엇인지 명확히 한다.
|
|
|
|
핵심 제약 하나: **Google이 Keycloak callback URI에 도달해야 함**. Google → Keycloak 사이는 redirect 기반이라 사용자 브라우저를 거치지만, 그 redirect URI는 **Google Cloud Console에 사전 등록된 HTTPS public URL**이어야 한다 (localhost 외에는 HTTP/raw IP 불가). 즉 P3A에서는 `localhost:8080`만으로도 됐지만 P3B는 **public domain + HTTPS**가 강제.
|
|
|
|
면접에서 답해야 할 질문:
|
|
1. P3A → P3B 추가 비용은? → public domain + TLS + ngrok/Cloudflare Tunnel 학습.
|
|
2. SPA 코드는 바뀌는가? → 안 바뀜. Keycloak이 Google과 OIDC로 통신, SPA는 늘 Keycloak token만 받음.
|
|
3. Keycloak이 발급하는 token의 issuer는? → 여전히 Keycloak (Google이 아님). audience도 SPA client.
|
|
|
|
- 이슈:
|
|
- PR:
|
|
|
|
<!-- section-id: branch-scope -->
|
|
## 범위
|
|
|
|
### 포함 범위
|
|
|
|
- P3A → P3B 차이만 (P3A 본문은 [[raw/branch-notes/feature-keycloak-single-ec2-no-google]]).
|
|
- 단일 EC2 + 외부 IdP의 제약 (public hostname, HTTPS, Google Console redirect URI 등록).
|
|
- public issuer·callback·reverse-proxy path가 서로 일치해야 한다는 배포 invariant와 [[raw/branch-notes/feature-keycloak-reverse-proxy-headers]] owner pointer.
|
|
- Google OAuth client의 Admin UI 표시 callback exact-match invariant와 [[raw/branch-notes/feature-keycloak-google-redirect-uri-policy]] owner pointer.
|
|
- public URL provider 선택(부모 D3)과 [[raw/branch-notes/feature-keycloak-public-domain-tunneling]] 운영 메커니즘 pointer.
|
|
- public HTTPS invariant와 [[raw/branch-notes/feature-keycloak-https-termination-caddy-nginx]] owner pointer.
|
|
- 토큰 교환 sequence (Keycloak ↔ Google brokering이 P3A flow에 삽입되는 위치).
|
|
|
|
### 제외 범위
|
|
|
|
- 실제 EC2 프로비저닝 / Google Cloud Console 등록 / cloudflared 데몬 구동 → 본 sub-branch 범위 밖.
|
|
- Google 외 외부 IdP (GitHub / Auth0 / Cognito).
|
|
- SAML brokering (OIDC만).
|
|
- multi-realm / multi-tenant.
|
|
|
|
## 근거 (필수, 최소 1개+)
|
|
|
|
> 본 sub-branch의 P3B (Single EC2 + Google federation) 채택 근거. 상세 대안 비교는 §외부 근거 / 대안 조사 참조.
|
|
|
|
| Source | 정당화하는 결정 |
|
|
|---|---|
|
|
| [[raw/official-docs/google-oauth2-redirect-uri-validation-official]] | Google OAuth redirect URI 검증 규칙 — public domain 확보 필요 근거 |
|
|
| [[raw/official-docs/keycloak-reverseproxy-official]] | Keycloak behind reverse proxy — proxy 헤더 설정 근거 |
|
|
| [[raw/official-docs/keycloak-hostname-configuration]] | Keycloak hostname guide — proxy 환경 추가 설정 근거 |
|
|
| [[raw/official-docs/keycloak-identity-brokering-overview-official]] | Identity Broker (P1B/P2B 공유) — Google IdP brokering 근거 |
|
|
| [[raw/official-docs/ngrok-http-tunnel-official]] | ngrok HTTP tunnel — 임시 public URL 근거 |
|
|
| [[raw/official-docs/cloudflare-tunnel-routing-official]] | Cloudflare Tunnel — ngrok 대안 (정적 도메인) 근거 |
|
|
|
|
## 컴포넌트 다이어그램
|
|
|
|
### 텍스트
|
|
|
|
```
|
|
EC2 (public IP / 도메인 필요)
|
|
├─ nginx or Caddy (port 80/443, TLS termination)
|
|
├─ Spring Boot (port 8081, Resource Server)
|
|
└─ Keycloak (port 8080, behind reverse proxy)
|
|
|
|
[1] Browser → EC2:443 → SPA load (HTML/JS, vanilla)
|
|
[2] Browser → EC2:443/keycloak/realms/{realm}/protocol/openid-connect/auth
|
|
→ Keycloak 로그인 화면 (사용자 "Google 로그인" 선택)
|
|
[3] Keycloak → 302 redirect → Google OIDC authorize endpoint (외부)
|
|
[4] Browser → Google 인증 UI → 사용자 동의
|
|
[5] Google → 302 redirect → EC2:443/keycloak/realms/{realm}/broker/google/endpoint
|
|
(=Keycloak broker endpoint, 반드시 public 접근 가능)
|
|
[6] Keycloak ← (server-to-server) Google /token endpoint → Google ID token + access token
|
|
[7] Keycloak이 Google user → Keycloak user 매핑 (first-login: 신규 생성)
|
|
[8] Keycloak → 302 redirect → SPA callback (Keycloak code)
|
|
[9] SPA → EC2:443/keycloak/realms/{realm}/protocol/openid-connect/token
|
|
→ Keycloak access token + refresh token + ID token
|
|
[10] Browser → EC2:443/api/* (Authorization: Bearer <keycloak-access-token>)
|
|
→ Spring Boot → Keycloak JWKS (localhost 내부) → 검증 → 응답
|
|
```
|
|
|
|
### Mermaid
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
autonumber
|
|
participant B as Browser (SPA)
|
|
participant N as nginx (EC2 :443)
|
|
participant K as Keycloak (EC2 :8080)
|
|
participant G as Google OIDC
|
|
participant API as Spring Boot (EC2 :8081)
|
|
|
|
B->>N: GET / (SPA load)
|
|
N-->>B: index.html
|
|
B->>N: GET /keycloak/.../auth
|
|
N->>K: proxy
|
|
K-->>B: 로그인 화면 (Google 선택지 포함)
|
|
B->>K: "Google 로그인" 선택
|
|
K-->>B: 302 redirect to Google authorize
|
|
B->>G: authorize (Google client_id)
|
|
G-->>B: 사용자 인증 + 동의
|
|
G-->>B: 302 redirect to https://kc.example.com/keycloak/.../broker/google/endpoint?code=...
|
|
B->>N: GET /keycloak/.../broker/google/endpoint?code=...
|
|
N->>K: proxy
|
|
K->>G: POST /token (code + client_secret) [server-to-server]
|
|
G-->>K: Google ID token + access token
|
|
K->>K: Google user → Keycloak user 매핑
|
|
K-->>B: 302 redirect to SPA callback (Keycloak code)
|
|
B->>N: GET /keycloak/.../token (code exchange)
|
|
N->>K: proxy
|
|
K-->>B: Keycloak access/refresh/ID token
|
|
B->>N: GET /api/orders (Bearer KC token)
|
|
N->>API: proxy
|
|
API->>K: JWKS fetch (localhost, 내부)
|
|
K-->>API: JWKS
|
|
API-->>B: 200 OK
|
|
```
|
|
|
|
## 토큰 교환 sequence (P3A 대비 추가 부분만)
|
|
|
|
P3A의 단계는 [[raw/branch-notes/feature-keycloak-single-ec2-no-google]]. 본 sub-branch는 **Keycloak ↔ Google brokering이 어디 끼는지**만 명확히.
|
|
|
|
- P3A 단계 1 (SPA → Keycloak /auth)까지 동일.
|
|
- P3A 단계 2 (사용자 로그인)에서 **사용자가 "Google" identity provider 선택** → 아래 brokering 분기 삽입:
|
|
- **B-1**: Keycloak이 사용자 브라우저를 Google `/authorize`로 302 redirect (Google client_id, Keycloak이 redirect_uri로 자기 broker endpoint 전달).
|
|
- **B-2**: 사용자가 Google에서 인증 → Google이 사용자 브라우저를 **Keycloak broker endpoint** (`/realms/{realm}/broker/google/endpoint?code=...`)로 302 redirect.
|
|
- **B-3**: Keycloak이 server-to-server로 Google `/token`에 code → Google ID token + access token 교환.
|
|
- **B-4**: Keycloak이 ID token claim(email 등)으로 Keycloak user를 lookup / first-login 시 신규 생성.
|
|
- 이후 P3A 단계 3-5 (Keycloak이 SPA에 code 발급 → SPA가 token exchange → SPA가 backend 호출)는 동일.
|
|
|
|
핵심: **SPA가 받는 token은 Google token이 아니라 Keycloak token**. Google token은 Keycloak이 보관 (broker link 정보).
|
|
|
|
## 단일 EC2 + Google federation 추가 제약
|
|
|
|
P3A 대비 늘어나는 운영 요구사항. 이게 P3B의 학습 포인트.
|
|
|
|
### 1) 공개 도메인 필수
|
|
|
|
- Google Cloud Console "Authorized redirect URIs"에 등록할 URL은 **HTTPS + 도메인** 형식 (localhost / raw IP 불가, 단 localhost는 dev 한정 일부 허용).
|
|
- 단일 EC2 학습 환경이라도 도메인 1개 + DNS A 레코드 → EC2 public IP 매핑 필요.
|
|
- 근거: [[raw/official-docs/google-oauth2-redirect-uri-validation-official]].
|
|
|
|
### reverse-proxy 정합
|
|
|
|
- 배포 invariant: 브라우저가 보는 public issuer·OIDC discovery·broker callback path와 proxy가 origin에 전달하는 host/scheme/path가 일치해야 한다. 환경변수·header·subpath의 정확한 값과 method 선택은 [[raw/branch-notes/feature-keycloak-reverse-proxy-headers]]가 소유하며 본 문서는 재진술하지 않는다.
|
|
|
|
### 3) 개발 환경 — stable public callback
|
|
|
|
- 부모 D3의 선택: 반복 가능한 Google callback은 **Cloudflare named tunnel + Cloudflare가 관리하는 custom domain**을 사용한다. `trycloudflare.com` quick tunnel과 ngrok random URL은 dev-only fallback이며 URL이 바뀔 때 Google Console callback도 함께 갱신한다. 명령·DNS·ingress 절차는 [[raw/branch-notes/feature-keycloak-public-domain-tunneling]]가 소유한다.
|
|
|
|
### 4) TLS termination
|
|
|
|
- 배포 invariant: Google에 등록하는 public callback은 HTTPS여야 하고 선택한 TLS termination 경로가 public scheme을 끝까지 보존해야 한다. Caddy/nginx/Cloudflare의 선택과 설정 상세는 [[raw/branch-notes/feature-keycloak-https-termination-caddy-nginx]]가 소유한다.
|
|
|
|
### 5) Google Cloud Console 등록
|
|
|
|
- 배포 invariant: Keycloak Admin UI가 표시한 broker callback 값을 Google Cloud Console에 그대로 등록하고 실제 요청값과 exact match시킨다. URL 조립·갱신·검증 절차는 [[raw/branch-notes/feature-keycloak-google-redirect-uri-policy]]가 소유하며 본 문서는 endpoint 문자열을 재구성하지 않는다.
|
|
|
|
## 장점 / 단점 vs P3A
|
|
|
|
### 장점
|
|
|
|
- **사용자 Google 로그인 가능**: Keycloak user store 외에 social login 1개 추가.
|
|
- **P3A 학습 + Google federation 학습 동시**: 단일 EC2의 단순함 + OIDC brokering의 핵심을 한 번에.
|
|
- **SPA 코드 영향 0**: SPA는 Keycloak만 호출. Identity provider 추가/제거는 Keycloak 측 설정.
|
|
- **token issuer가 Keycloak으로 통일**: backend는 Google JWT를 직접 검증할 필요 없음 (Keycloak이 broker).
|
|
|
|
### 단점
|
|
|
|
- **public 도메인 + HTTPS 요구**: 학습 friction +1 (P3A는 localhost로 끝남).
|
|
- **random URL 갱신 friction**: ngrok/quick tunnel URL이 바뀌면 Google Console callback도 갱신해야 함. 반복 학습은 D3의 Cloudflare named tunnel + managed custom domain 사용.
|
|
- **운영 surface 증가**: Google client_secret 관리, Keycloak hostname 잘못 설정 시 invalid_redirect_uri 디버깅 비용.
|
|
- **사용자 매핑 정책 결정**: Google email → 기존 Keycloak user 자동 link 여부 (first-login flow 설정).
|
|
|
|
## TODO
|
|
|
|
각 항목 옆에 증거 등급 표기: `actually-implemented` | `locally-verified` | `prod-verified` | `documented-only` | `planned` | `needs-confirmation`
|
|
|
|
- [x] P3A→P3B brokering 분기 삽입 위치 sequence 명세 (§토큰 교환 sequence) — 등급: `documented-only`
|
|
- [x] 단일 EC2 + Google federation 추가 제약 5종 정리 (§단일 EC2 추가 제약) — 등급: `documented-only`
|
|
- [x] 컴포넌트/시퀀스 다이어그램 (§컴포넌트 다이어그램) — 등급: `documented-only`
|
|
- [x] 대안 5종 비교 조사 (§외부 근거 / 대안 조사) — 등급: `documented-only`
|
|
- [ ] 실 EC2 프로비저닝 + Google client 등록 + cloudflared 구동 — 등급: `planned` (본 sub-branch **범위 밖**, P3A 완료 후 선택 확장)
|
|
- [ ] §Claims To Verify 6종 실 기동 검증 — 등급: `planned` (실 배포 시점에만 가능)
|
|
|
|
## 진행 중 메모
|
|
|
|
- 본 sub-branch 는 **documented-only** (D1). 실 배포(EC2 / Google client / cloudflared)는 P3A 완료 후 선택 확장 — 여기서는 config recipe + sequence + 제약만 명세한다.
|
|
- P3A([[raw/branch-notes/feature-keycloak-single-ec2-no-google]]) 토폴로지에 Google brokering 분기만 삽입 — 새로 생기는 요구는 "public 접근 가능한 callback URL 도달성" 하나뿐(§목표, §토큰 교환 sequence).
|
|
- §구현 가이드는 child owner pointer와 deploy invariant만 제공한다. 각 child의 config/명령 및 결합 chain은 실 기동 검증 전까지 `actually-implemented`로 승격하지 않는다.
|
|
|
|
## 결정 사항 (decisions)
|
|
|
|
- **2026-05-25**: 본 패턴은 **문서 + 다이어그램까지만**. 실 구현(EC2 프로비저닝, Google client 등록, cloudflared 구동)은 진행하지 않음. 이유: root branch가 "P3A 한정 구현"으로 결정 → P3B는 P3A 완료 후 선택적 확장.
|
|
- **2026-05-25**: P3B의 핵심 학습 포인트를 "Google이 Keycloak callback URI에 도달해야 한다는 제약" 단 한 줄로 압축. 나머지(hostname, proxy headers, ngrok 등)는 그 제약의 파생.
|
|
- **2026-07-18 (D3 owner clarification)**: stable Google callback의 기본 경로는 **Cloudflare named tunnel + Cloudflare-managed custom domain**이다. `trycloudflare.com` quick tunnel과 ngrok random URL은 dev-only fallback이며 URL 회전 시 Google Console callback 갱신이 필요하다. provider 우선순위는 본 부모 D3가 소유하고, 운영 명령·DNS 메커니즘은 child가 소유한다.
|
|
- **2026-05-25**: Keycloak ↔ Google brokering 흐름은 P1B / P2B와 **OIDC sequence 동일** — 차이는 "어디에 Keycloak이 떠 있나"뿐. 본 sub-branch는 P3A 토폴로지 + brokering 분기 삽입 위치만 명시.
|
|
|
|
## 결정-근거 매핑
|
|
|
|
> 각 결정이 어떤 raw source claim 으로 뒷받침되는지. P3B 는 문서/다이어그램 단계라 일부 결정은 우선순위/scope 기반 → `UNSUPPORTED_DECISION`.
|
|
|
|
| Decision ID | Decision | Supporting Claims | Evidence Strength | Open Risk |
|
|
|---|---|---|---|---|
|
|
| D1 | 본 패턴은 문서 + 다이어그램까지만 (실 EC2/Google client/cloudflared 구동 안 함) | `UNSUPPORTED_DECISION` — 학습 scope 결정으로 공식 근거 대상 아님 | (project scope 결정) | 실 구현 없이 문서만으로 면접 답변 시 "직접 해본 것"으로 오해 금지 — `documented-only` 등급 명시 필수 |
|
|
| D2 | P3B 의 핵심 학습 포인트를 "Google 이 Keycloak callback URI 에 도달해야 한다" 한 줄로 압축 | `raw/official-docs/google-oauth2-redirect-uri-validation-official.md#GOOGLE-REDIR-C1`, `raw/official-docs/google-oauth2-redirect-uri-validation-official.md#GOOGLE-REDIR-C2`, `raw/official-docs/google-oauth2-redirect-uri-validation-official.md#GOOGLE-REDIR-C3` | `official-vendor-doc` | "한 줄로 압축" 자체는 학습 framing — 실 구현 시 hostname/proxy headers 가 추가 결정점으로 부각될 수 있음 |
|
|
| D3 | **public URL provider 선택 owner** — stable Google callback은 Cloudflare named tunnel + managed custom domain, random quick tunnel/ngrok는 dev-only fallback | `raw/official-docs/cloudflare-tunnel-routing-official.md#CLOUDFLARE-TUNNEL-C1`, `raw/official-docs/cloudflare-tunnel-routing-official.md#CLOUDFLARE-TUNNEL-C2`, `raw/official-docs/cloudflare-tunnel-routing-official.md#CLOUDFLARE-TUNNEL-C4`, `raw/official-docs/ngrok-http-tunnel-official.md#NGROK-C1`, `raw/official-docs/ngrok-http-tunnel-official.md#NGROK-C3`, `raw/official-docs/ngrok-http-tunnel-official.md#NGROK-C4`, `raw/official-docs/google-oauth2-redirect-uri-validation-official.md#GOOGLE-REDIR-C3`, `raw/official-docs/google-oauth2-redirect-uri-validation-official.md#GOOGLE-REDIR-C5` | `official-vendor-doc + official-vendor-doc + official-vendor-doc` | managed custom domain의 Google 등록과 end-to-end callback은 미검증. child [[raw/branch-notes/feature-keycloak-public-domain-tunneling]]는 이 선택을 소비해 운영 profile만 소유 |
|
|
| D4 | **reverse-proxy deploy invariant** — public issuer·discovery·broker callback의 host/scheme/path가 proxy가 전달하는 값과 일치해야 함. [[raw/branch-notes/feature-keycloak-reverse-proxy-headers]] D1 — proxy-header parsing invariant. [[raw/branch-notes/feature-keycloak-reverse-proxy-headers]] D3 — public path assembly invariant | `raw/official-docs/keycloak-hostname-configuration.md#KC-HOST-C2`, `raw/official-docs/keycloak-hostname-configuration.md#KC-HOST-C5`, `raw/official-docs/keycloak-reverseproxy-official.md#KC-RP-C2`, `raw/official-docs/keycloak-reverseproxy-official.md#KC-RP-C3`, `raw/official-docs/keycloak-reverseproxy-official.md#KC-RP-C4`, `raw/official-docs/keycloak-reverseproxy-official.md#KC-RP-C5`, `raw/official-docs/keycloak-reverseproxy-official.md#KC-RP-C6` | `official-vendor-doc + delegated detail` | target proxy chain에서 discovery issuer와 callback을 실측하고 child decision과 대조 필요 |
|
|
| D5 | Google `email_verified=true` + `hd` 정책 검사 + First Broker Login Flow 로 사용자 매핑 | `raw/official-docs/keycloak-identity-brokering-overview-official.md#KC-IDP-BROKER-C1`, `raw/official-docs/keycloak-first-broker-login-flow.md#KC-FBL-C1`, `raw/official-docs/google-oidc-discovery-spec.md#GOOGLE-OIDC-C5`, `raw/official-docs/google-oidc-discovery-spec.md#GOOGLE-OIDC-C7` | `official-vendor-doc` | `KC-FBL-C2`/`C3`/`C4` 는 모두 `needs-confirmation` 상태 (2026-05-25 quote, 재검증 보류). 실 동작 검증 시 first-broker-login authenticator UI 직접 확인 필요 |
|
|
| D6 | **redirect deploy invariant** — Keycloak Admin UI가 표시한 broker callback을 Google Cloud Console에 그대로 등록하고 실제 요청값과 exact match시킴. URL 조립·갱신 정책은 [[raw/branch-notes/feature-keycloak-google-redirect-uri-policy]] 소유 | `raw/official-docs/keycloak-identity-brokering-overview-official.md#KC-IDP-BROKER-C2`, `raw/official-docs/google-oauth2-redirect-uri-validation-official.md#GOOGLE-REDIR-C3` | `needs-confirmation + official-vendor-doc + delegated detail` | Admin UI 표시값과 실제 요청값의 target-version 일치 여부를 실 로그인으로 확인 필요 |
|
|
|
|
## 구현 가이드
|
|
|
|
> 본 sub-branch 는 **documented-only** (D1) — 아래는 *실 배포 시 되묻지 않을 config recipe* 의 사전 명세이며, 어느 항목도 아직 기동 검증되지 않았다(각 등급 열 = `planned`/`needs-confirmation`). 값의 상세 서술 owner 는 §단일 EC2 + Google federation 추가 제약 이고, 여기서는 **Trace(D-ID + Claim ID) + 임의결정 라벨**만 정리한다(재진술 금지).
|
|
|
|
### 1. Reverse-proxy / hostname integration contract (Reference-Only)
|
|
|
|
> **Trace**: D4 + `KC-HOST-C2/C5`, `KC-RP-C2..C6`.
|
|
|
|
- 본 부모가 유지하는 것은 **public issuer·discovery·broker callback의 host/scheme/path가 proxy 전달값과 일치한다**는 deploy invariant 한 줄이다.
|
|
- [[raw/branch-notes/feature-keycloak-reverse-proxy-headers]] D1 — proxy-header parsing invariant. [[raw/branch-notes/feature-keycloak-reverse-proxy-headers]] D3 — public path assembly invariant.
|
|
- [[raw/branch-notes/feature-keycloak-header-spoofing-defense]] D6 — trusted proxy boundary owner. [[raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch]] D6 — hostname/issuer bridge profile owner. TLS 종단은 [[raw/branch-notes/feature-keycloak-https-termination-caddy-nginx]]가 소유한다. 본 문서에서 값을 복제하지 않는다.
|
|
|
|
### 2. Public callback URL 선택 contract (Reference-Only)
|
|
|
|
> **Trace**: D3 + `CLOUDFLARE-TUNNEL-C1/C2/C4`, `NGROK-C1/C3/C4`, `GOOGLE-REDIR-C3/C5`.
|
|
|
|
- stable callback은 Cloudflare named tunnel + managed custom domain, random quick tunnel/ngrok는 dev-only fallback이라는 **선택**만 본 부모 D3가 소유한다.
|
|
- tunnel 생성·DNS·ingress·실행 명령과 fallback 운영 절차는 [[raw/branch-notes/feature-keycloak-public-domain-tunneling]] D1/D2가 소유한다.
|
|
|
|
### 3. Google callback 등록 contract (Reference-Only)
|
|
|
|
> **Trace**: D6 + `KC-IDP-BROKER-C2`, `GOOGLE-REDIR-C3`.
|
|
|
|
- Keycloak Admin UI가 표시한 callback 문자열을 Google Cloud Console에 그대로 등록하고 실제 요청값과 exact match시킨다.
|
|
- client type, endpoint URL 조립, trailing slash/case, URL 회전 시 갱신 절차는 [[raw/branch-notes/feature-keycloak-google-redirect-uri-policy]] D1/D8이 소유한다. 본 부모는 특정 endpoint 문자열을 copyable 값으로 제공하지 않는다.
|
|
|
|
### 4. First Broker Login 사용자 매핑 (consume-only — 정책 owner 는 sibling branch)
|
|
|
|
> **Trace**: 본 sub-branch(P3B 토폴로지)는 first-broker-login flow 를 **consume** 만 한다 — 매칭키·소유증명·auto-link 정책 자체는 **다른 branch 소유**(아래 위임)이며 여기서 재정의하지 않는다. 소비 지점 근거: `keycloak-first-broker-login-flow#KC-FBL-C1` (First login flow 존재) · `google-oidc-discovery-spec#GOOGLE-OIDC-C6` (sub = unique primary key) · `#GOOGLE-OIDC-C7` (hd = Workspace 도메인) · `keycloak-identity-brokering-overview-official#KC-IDP-BROKER-C1`.
|
|
>
|
|
> - **OUT_OF_BRANCH_SCOPE (위임, 재정의 금지)**: (a) linking key `email` vs `sub` → [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]] `D1` (sub-only 결정, core owned) 소유. (b) 기존 local 계정 link 시 password 재인증 / Confirm Link Existing Account authenticator → [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] `D2` + [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]] `D2` 소유. (c) `email_verified=false` silent auto-link 차단 → [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] `D4` (AutoLink DISABLED = core) 소유.
|
|
|
|
- **P3B 범위 consume 지점**: Google ID token claim(`email`, `email_verified`, `hd`, `sub`)이 single-EC2 배치의 Keycloak first-broker-login flow 로 유입 → user lookup / first-login 신규 생성. 매칭·link 정책의 종결은 위 owner 브랜치(§엣지·실패·의존 "다른 계약 의존"에도 링크). 본 노트는 그 정책이 *어느 배치에서든 동일하게* 적용됨을 전제로 P3A 토폴로지 위에서 flow 를 실행할 뿐.
|
|
|
|
## 엣지·실패·의존
|
|
|
|
> R4 캡처용. 정상 sequence(§컴포넌트 다이어그램) 외에 실 배포 시 부딪힐 실패/엣지 + 다른 branch 계약 의존. 본 sub-branch 는 documented-only 이므로 아래는 *실 기동 시 예상되는* 경로다(§Claims To Verify 가 검증 방법 owner).
|
|
|
|
- **실패·엣지 경로**:
|
|
- `redirect_uri_mismatch`: Google Console 등록 URI와 Admin UI 표시 callback이 다르면 로그인 거부. 기대 동작과 갱신 절차는 [[raw/branch-notes/feature-keycloak-google-redirect-uri-policy]] D1/D8을 따른다.
|
|
- reverse-proxy public context 유실: proxy chain 어느 홉에서든 public host/scheme/path 계약이 깨지면 discovery·issuer·callback 정합이 무너진다. 정확한 header/env 검증은 [[raw/branch-notes/feature-keycloak-reverse-proxy-headers]]가 소유한다.
|
|
- random URL 회전: dev-only fallback URL이 바뀌면 등록 callback이 stale해진다. 반복 사용은 D3의 named tunnel + managed custom domain으로 전환한다.
|
|
- proxy-header spoofing: trusted proxy 경계가 잘못되면 외부 입력이 public URL 계산에 개입할 수 있다. 구체 방어값과 검증은 [[raw/branch-notes/feature-keycloak-header-spoofing-defense]] D6이 소유한다.
|
|
- first-broker-login 중복 email: 같은 email 의 local user 선점 시 무단 link 위험 → "Confirm Link Existing Account" authenticator 필요(`KC-FBL-C2`, §Claims To Verify #6).
|
|
- **다른 계약 의존**:
|
|
- [[raw/branch-notes/feature-keycloak-single-ec2-no-google]] (P3A) 의 **단일 EC2 reverse-proxy 토폴로지** 에 의존 — 본 브랜치는 그 base 에 brokering 분기만 삽입(§목표). P3A 의 nginx/port 배치 결정이 바뀌면 본 브랜치 config(§구현 가이드 1) 영향.
|
|
- [[raw/branch-notes/feature-keycloak-edge-forwardauth-google-federation]] (P1B) · [[raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation]] (P2B) 와 **동일 OIDC brokering sequence** — 차이는 배치뿐(§결정 사항 4). brokering flow 결정이 바뀌면 세 브랜치 공동 갱신.
|
|
- **account-linking / first-broker-login 정책 의존** (§구현 가이드 4 consume-only): [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]] `D1`(sub-only 매칭키)·`D2`(기존계정 link 재인증) + [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] `D2`(Confirm Link)·`D4`(email_verified auto-link 차단) 가 소유. 본 브랜치는 그 정책을 배치 무관하게 consume — 정책이 바뀌면 본 노트 §구현 가이드 4 의 consume 서술도 갱신.
|
|
- sub-sub-branch 관심사 위임: [[raw/branch-notes/feature-keycloak-public-domain-tunneling]](tunnel 상세) · [[raw/branch-notes/feature-keycloak-reverse-proxy-headers]](proxy header 상세) · [[raw/branch-notes/feature-keycloak-google-redirect-uri-policy]](redirect URI 정책) · [[raw/branch-notes/feature-keycloak-https-termination-caddy-nginx]](TLS termination) 가 각 관심사 detail owner.
|
|
|
|
## 검증해야 할 주장
|
|
|
|
> 공식 문서는 P3B 의 각 요소를 보장하지만, 전체 chain (Cloudflare Tunnel → nginx → Keycloak → Google) 의 결합 동작은 실 구현 시점에서만 검증 가능.
|
|
|
|
| Claim | Why uncertain | How to verify | Status |
|
|
|---|---|---|---|
|
|
| Cloudflare named tunnel + managed custom domain이 Google callback 등록과 실제 brokering에 통과 | 공식 자료는 각 구성요소를 다루지만 결합 chain은 보장하지 않음 | child [[raw/branch-notes/feature-keycloak-public-domain-tunneling]]의 acceptance 절차로 managed hostname 등록·실 login 확인 | `needs-confirmation` |
|
|
| proxy chain이 public host/scheme/path를 보존해 discovery issuer와 callback이 동일 public context를 사용하는지 | Keycloak의 parsing ability와 전체 chain 결합 동작은 별개 | [[raw/branch-notes/feature-keycloak-reverse-proxy-headers]]의 discovery·header acceptance 결과를 본 D4 invariant와 대조 | `planned` |
|
|
| Keycloak Admin UI 표시 callback과 Google Cloud Console 등록값이 exact match해 실제 login이 성공하는지 | `GOOGLE-REDIR-C3` 정책은 확보했지만 target-version UI 값과 실제 요청 결합은 미검증 | [[raw/branch-notes/feature-keycloak-google-redirect-uri-policy]]의 exact-match test 수행 | `needs-confirmation` |
|
|
| child owner의 trusted-proxy 설정이 외부 spoofing을 차단하는지 | 옵션 존재와 실제 drop/ignore/log 동작은 별개 | [[raw/branch-notes/feature-keycloak-header-spoofing-defense]] D6의 negative test 결과 참조 | `planned` |
|
|
| Keycloak first-broker-login flow 가 Account Linking 시 "비밀번호 확인 후 link" 정책으로 실제 작동 | `KC-FBL-C2` 등이 `needs-confirmation` 등급 — 정책 UI 토글 위치/동작 불확실 | Admin UI → Authentication → First Broker Login → flow copy + Confirm Link Existing Account authenticator 추가 → 같은 email 의 local user 사전 생성 후 Google 로그인 시도 | `needs-confirmation` |
|
|
|
|
## 마주친 문제
|
|
|
|
- 아직 없음 (문서 단계).
|
|
|
|
## 묶음 (자식 sub-sub-branches)
|
|
|
|
<!-- GENERATED: sources:start -->
|
|
- [[raw/official-docs/cloudflare-tunnel-routing-official]]
|
|
- [[raw/official-docs/google-oauth2-redirect-uri-validation-official]]
|
|
- [[raw/official-docs/keycloak-hostname-configuration]]
|
|
- [[raw/official-docs/keycloak-identity-brokering-overview-official]]
|
|
- [[raw/official-docs/keycloak-reverseproxy-official]]
|
|
- [[raw/official-docs/ngrok-http-tunnel-official]]
|
|
<!-- GENERATED: sources:end -->
|
|
|
|
<!-- GENERATED: branches:start -->
|
|
<!-- GENERATED: branches:end -->
|
|
|
|
- [[raw/branch-notes/feature-keycloak-public-domain-tunneling]]
|
|
- [[raw/branch-notes/feature-keycloak-reverse-proxy-headers]]
|
|
- [[raw/branch-notes/feature-keycloak-google-redirect-uri-policy]]
|
|
- [[raw/branch-notes/feature-keycloak-https-termination-caddy-nginx]]
|
|
|
|
> Sources는 하단 "외부 근거" 섹션. Errors/Interview/Lectures/Blog 는 현재 없음.
|
|
|
|
## 관련 일일 노트
|
|
|
|
|
|
## 관련 sub-branch
|
|
|
|
- [[raw/branch-notes/feature-keycloak-patterns]] (root)
|
|
- [[raw/branch-notes/feature-keycloak-single-ec2-no-google]] — P3A Single EC2 (no Google) **← P3B의 베이스 토폴로지, vanilla JS 구현 대상**
|
|
- [[raw/branch-notes/feature-keycloak-edge-forwardauth-google-federation]] — P1B Edge + Google federation (다른 배치, 동일 brokering 흐름)
|
|
- [[raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation]] — P2B Internal + Google federation (다른 배치, 동일 brokering 흐름)
|
|
|
|
## 외부 근거 / 대안 조사 (2026-05-25 — P3B Single EC2 + Google IdP Brokering)
|
|
|
|
본 sub-branch의 **단일 EC2 + Google IdP Brokering** 채택에 대한 외부 source. P3A에 federation 추가 시 발생하는 **public 도메인 + HTTPS 요구사항** 중심.
|
|
|
|
- **채택 결정 (Single EC2 + Public Domain + Keycloak Reverse Proxy 설정 + Google IdP)**:
|
|
- [[raw/official-docs/google-oauth2-redirect-uri-validation-official]] — Google OAuth client redirect URI 검증 규칙 (localhost test-only, prod HTTPS 필수)
|
|
- [[raw/official-docs/keycloak-reverseproxy-official]] — Keycloak behind reverse proxy (KC_PROXY_HEADERS, KC_HTTP_RELATIVE_PATH)
|
|
- [[raw/official-docs/keycloak-hostname-configuration]] — Keycloak hostname guide (proxy 환경 추가 설정)
|
|
- [[raw/official-docs/keycloak-identity-brokering-overview-official]] — Identity Broker (P1B/P2B 공유 source)
|
|
- [[raw/official-docs/ngrok-http-tunnel-official]] — ngrok HTTP tunnel (개발 환경 임시 public URL)
|
|
- [[raw/official-docs/cloudflare-tunnel-routing-official]] — Cloudflare Tunnel (ngrok 대안, 정적 도메인)
|
|
- **검토한 대안**:
|
|
- **대안 1: P3A 유지 (no Google federation)** — Google 학습을 별도 sub-project로. 장: 학습 friction 최소 / 단: federation 학습 누락. 비교 sub-branch: [[raw/branch-notes/feature-keycloak-single-ec2-no-google]].
|
|
- **대안 2: localhost-only + Google Workspace SAML** — Workspace SAML은 일부 환경에서 localhost 허용. 그러나 일반 Google 계정은 OIDC만 + localhost 제한.
|
|
- **대안 3: AWS EC2 public IP + Route53 도메인 + ACM cert** — production-like. 장: HTTPS termination 학습 / 단: AWS 비용 + cert provisioning 시간.
|
|
- **대안 4: K8s + cert-manager + Let's Encrypt (P2B 진화)** — 분리 배치 + 자동 cert. 장: prod-like / 단: P3 목적(단일 host 학습)과 어긋남.
|
|
- **대안 5: Cognito + Google federation (Keycloak 제거)** — AWS managed. 본 학습 목적에 부적합.
|
|
- **비교 핵심**: P3A 대비 추가되는 핵심 운영 요구는 **public HTTPS callback URL**이다. 반복 가능한 학습 환경은 D3의 Cloudflare named tunnel + managed custom domain을 사용하고, random quick tunnel/ngrok는 dev-only fallback으로 취급한다. public issuer·proxy context는 [[raw/branch-notes/feature-keycloak-reverse-proxy-headers]], TLS는 [[raw/branch-notes/feature-keycloak-https-termination-caddy-nginx]], callback exact-match는 [[raw/branch-notes/feature-keycloak-google-redirect-uri-policy]]의 결정과 검증을 소비한다.
|
|
|
|
## 완료 후 정리
|
|
|
|
> 본 sub-branch는 **문서까지만**. wiki 추출은 root branch의 6 패턴 비교 매트릭스 시점에 일괄 처리.
|
|
|
|
- PR 링크:
|
|
- 리뷰 메모:
|
|
- 머지 결과 / 배포 환경: 해당 없음 (문서 단계, P3B 실 구현 안 함).
|
|
- **wiki 추출 대상** (verified만, `wiki/projects/`로만 추출):
|
|
- `actually-implemented` 항목: 없음
|
|
- `locally-verified` 항목: 없음
|
|
- `prod-verified` 항목: 없음
|
|
- **추출하지 않을 항목** (planned / documented-only / abandoned):
|
|
- 본 sub-branch 전체가 `documented-only` 등급. 추후 root branch의 6 패턴 비교 매트릭스에 인용되는 형태로만 활용. `wiki/projects/` 직접 승급 없음.
|