45 KiB
title, source_type, status, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, branch, parent_branch, related_projects, tags, created, target_merge, status_label, contract_packet_sha256
| title | source_type | status | id | kind | project | work_item | inherits | refines | overrides | depends_on | contract_packet | branch | parent_branch | related_projects | tags | created | target_merge | status_label | contract_packet_sha256 | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| branch / feature-keycloak-single-ec2-no-google (P3A Single EC2 — client + backend + keycloak 동거, no Google) | branch-note | raw | BR-KEYCLOAK-CHILD-FE8F0749 | branch-child | keycloak-patterns-overview | WI-KEYCLOAK-PATTERNS-OVERVIEW-020 |
|
1 | feature-keycloak-single-ec2-no-google | feature-keycloak-patterns |
|
|
2026-05-25 | in-progress | 930562ffd6f26bab1c938d08a2d2403cdc3627a5b242ffabe05057a32f792e17 |
branch: feature-keycloak-single-ec2-no-google (P3A Single EC2, no Google)
Layer:
raw/branch-notes/— raw/branch-notes/feature-keycloak-patterns의 WI020 child branch. 본 패턴은 6개 패턴 중 유일하게 vanilla JS로 실 구현되는 케이스. 나머지 5개(P1A/P1B/P2A/P2B/P3B)는 문서/다이어그램까지만. 축 재편 (2026-07-14): hub raw/project-notes/keycloak-patterns-overview §2.3 에서 P3A → AP1 (Browser-based OAuth Client = SPA-direct + Resource Server) + cross-cutting배포=single-EC2 (실 구현 base)로 re-map 됨. hub 고정 결정 F5(§5)가 "E2E 실 구현 배포 = single-EC2 docker-compose 1벌" 로 확정 → 본 노트는 4 패턴(AP1~AP4) 전체가 얹히는 물리 배포 base 이다(그 위 실행 단계는 6개 자식이 owner — §구현 가이드 §3). 본문의 "P3A" 프레이밍은 Phase 0 표기이며, rename/re-parent 은 hub §2.3·§8 대로wiki-doc-author mode=migrate로 점진 수행(§진행 중 메모AXIS_DRIFT).
부모 (필수)
raw/branch-notes/feature-keycloak-patterns
브랜치 계약 패킷
- 생성 시 프로젝트 개정:
1 - 패킷 스키마:
contract_packet: 1 - 완료 조건: project governance hub가 AP1~AP4 taxonomy와 child progress index를 유지한다
상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
DEC-KEYCLOAK-PATTERNS-OVERVIEW-AUTH-TAXONOMY-001@1 |
canonical 분류축은 AP1~AP4 인증 통합 아키텍처와 cross-cutting 변형이다 | single-EC2를 AP1~AP4가 공유하는 deployment cross-cutting 변형으로 분류한다 | raw/project-notes/keycloak-patterns-overview |
브랜치 지역 결정
기존 branch-local 결정은 아래
## Decision Evidence Map / 결정-근거 매핑의 D-row가 소유하며 이 packet에서 복제하지 않는다.
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|
선언한 예외
| Override ID | Overrides | Reason | Approval | Status |
|---|
없음.
목표
단일 EC2 호스트에 client (vanilla JS SPA via nginx static) + backend (Spring Boot) + Keycloak 세 컴포넌트를 동거시킨 상태에서, OIDC Authorization Code + PKCE 흐름이 실제로 어떻게 동작하는지 코드 레벨로 학습. 토큰 교환 흐름은 P2A와 동일(Browser → Keycloak → Backend Resource Server JWT validation). 차이는 네트워크 토폴로지와 KC_HOSTNAME 함정.
면접에서 "OIDC 전체 lifecycle을 직접 구현해 봤다 → access/refresh/ID token 차이, PKCE 필요 이유, JWT issuer 검증 메커니즘을 코드로 설명 가능"이 목표.
범위
포함 범위
- 단일 EC2 배포 토폴로지 확정 (본 branch 고유 소유) — nginx(SPA static) + Spring Boot(Resource Server) + Keycloak + PostgreSQL 를 단일 호스트 docker-compose 로 동거시키는 물리 경계·포트 노출·localhost trust 확정(D3, hub F5). hub 재편 후 4 패턴(AP1~AP4) E2E 가 모두 이 배포 base 위에 얹힌다.
KC_HOSTNAMEiss 함정의 배포측 정의 — 단일 host 에서 browser 와 backend 가 같은 issuer hostname 을 봐야 하는 이유·해결 축 확정(D3). 재현·해결 절차 자체는 자식 raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch 소관.- HTTPS-less 학습 경계 확정 (D1) — 학습 환경은 HTTP, prod 진입 시 Caddy / nginx + Let's Encrypt 로 termination 추가.
- vanilla JS 로 OIDC lifecycle 을 실제로 구현하는 유일 케이스 — 6 실 구현 단계(자식)로 분해(§Cluster), 각 단계가
planned→ 구현 후locally-verified승급. - 문서 산출물 — 컴포넌트 토폴로지 · 토큰 교환 sequence · 단일 호스트 특이점(
KC_HOSTNAME/redirect_uri) · 장단점 (모두 현재planned/documented-only).
제외 범위
의도적으로 제외한 것. 면접 등에서 "이건 범위에 없었습니다"라고 답할 근거.
- 실행 코드 detail — 본 노트는 6 단계의 통합 배포 base 이며 detail 은 재진술하지 않고 위임한다(Reference-Only, §구현 가이드 §3): docker-compose 서비스 정의 → raw/branch-notes/feature-keycloak-docker-compose-stack D1
D6, realm/client 설정+export → raw/branch-notes/feature-keycloak-realm-client-export D1D5, SPA PKCE 코드 → raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce D1D5, Spring RS 공통 셋업·audience 검증 → raw/branch-notes/feature-keycloak-spring-rs-audience-validator D1/D6, RBAC → raw/branch-notes/feature-keycloak-spring-rs-role-mapping D4D6, iss 재현·해결 → raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch D1/D4/D6, refresh rotation+logout → raw/branch-notes/feature-keycloak-refresh-rotation-and-logout D1/D5. - Google IdP federation — raw/branch-notes/feature-keycloak-single-ec2-google-federation (P3B) 소관. 본 노트는 no-google base.
- cluster-internal / edge 배포의 별도 구축 — hub F5 에 의해 문서만(hostname·issuer·network 차이). 실 구축(k8s / Traefik)은 안 함.
- AP2/AP3/AP4 인증 아키텍처 자체의 정의 — 본 노트는 배포 base 이지 그 패턴 hub 가 아니다. AP1 pattern hub 는 raw/branch-notes/feature-keycloak-internal-spa-direct-no-google (P2A).
- RBAC 인가 (keycloak role → Spring
@PreAuthorize) — hub §5 deferred(authZ). raw/branch-notes/feature-keycloak-spring-rs-role-mapping 의 role→role 부분이 여기로 이월. - prod 배포 / HA cluster / 실제 HTTPS 구성 — 전부
planned. 본 회차 범위는 로컬 docker-compose(또는 단일 EC2) 학습 검증까지.
근거 (필수, 최소 1개+)
본 sub-branch의 P3A (Single EC2, 실 구현 대상) 채택 근거. 상세 대안 비교는 §외부 근거 / 대안 조사 참조.
| Source | 정당화하는 결정 |
|---|---|
| raw/official-docs/keycloak-server-containers-docker | Keycloak Docker container 공식 — docker-compose 채택 근거 |
| raw/official-docs/keycloak-hostname-configuration | Keycloak hostname guide — KC_HOSTNAME 설정 근거 |
| raw/official-docs/keycloak-getting-started-docker | Docker quickstart — 단일 host 학습 구성 근거 |
| raw/official-docs/spring-security-resource-server-jwt | Spring Security Resource Server JWT 검증 — backend Resource Server 근거 |
| raw/official-docs/oauth2-pkce-rfc-7636 | RFC 7636 PKCE — public client 필수 PKCE 근거 |
| raw/official-docs/oidc-client-ts-library | oidc-client-ts — vanilla JS OIDC client 라이브러리 선택 근거 |
외부 근거 / 대안 조사 (2026-05-25 — P3A Single EC2)
본 sub-branch의 단일 EC2 (client + backend + keycloak 동거) + Authorization Code + PKCE 채택에 대한 외부 source. P2A를 단일 호스트로 압축한 형태 + 단일 호스트 고유 함정.
- 채택 결정 (Single Host Docker Compose + PKCE + Keycloak
KC_HOSTNAME):- raw/official-docs/keycloak-server-containers-docker — Keycloak Docker container 공식 (KC_* 환경 변수)
- raw/official-docs/keycloak-hostname-configuration — Keycloak hostname guide (iss claim validation 함정)
- raw/official-docs/keycloak-getting-started-docker — Docker quickstart (단일 host 학습용)
- raw/official-docs/spring-security-resource-server-jwt — Spring Security Resource Server JWT 검증
- raw/official-docs/oauth2-pkce-rfc-7636 — RFC 7636 PKCE (public client 필수)
- raw/official-docs/oidc-client-ts-library — oidc-client-ts (vanilla JS OIDC client 라이브러리 선택지)
- 검토한 대안:
- 대안 1: Cluster 배치 (P2A) — Kubernetes 또는 ECS로 분리 배치. 장: prod-like / 단: 학습 friction 큼 (네트워크 / DNS / cert 모두 관리). 비교 sub-branch: raw/branch-notes/feature-keycloak-internal-spa-direct-no-google.
- 대안 2: Edge ForwardAuth on Single Host — nginx + oauth2-proxy + Keycloak + backend 모두 단일 host. 장: P1A 학습 가능 / 단: vanilla JS SPA 흐름 학습이 주 목적과 어긋남 (proxy가 인증 처리, SPA는 token 모름).
- 대안 3: BFF on Single Host — Spring Boot이 Keycloak token holder. 장: 보안 우월 (token이 SPA에 없음) / 단: vanilla JS의 OIDC 학습 목적과 어긋남 (SPA가 session cookie만 사용).
- 대안 4: Direct host (no Docker) — Keycloak + Spring Boot + nginx를 EC2에 직접 설치. 장: docker overhead 0 / 단: 환경 reset 어려움, 학습 반복 비용 큼.
- 대안 5: 사전 빌드 이미지 (Keycloak Helm + Spring Boot Image) — managed Keycloak. 학습 단계엔 과함.
- 비교 핵심: 단일 EC2 + Docker Compose는 OIDC 전체 lifecycle을 가장 작은 surface로 학습.
KC_HOSTNAME미설정 시issclaim mismatch가 단일 host의 가장 흔한 함정 — browser는localhost:8080, backend는 Docker internalkeycloak:8080보면서 JWT issuer가 mismatch → JWT validation 실패. 해결:KC_HOSTNAME=localhost+KC_HTTP_ENABLED=true명시. redirect_uri는 localhost vs 127.0.0.1 한 글자만 달라도 mismatch → Keycloak client 등록 시 두 URI 모두 등록 또는 사용 일관화. PKCE는 public client에 필수 (RFC 7636) —code_verifier생성 +code_challenge=SHA256(verifier).base64url. HTTPS 없이 학습 환경 한정 — prod 진입 시 Caddy 또는 nginx + Let's Encrypt 필수.
TODO
각 항목 옆에 증거 등급. 현재 모두 planned — 실 구현 후 별도 작업에서 actually-implemented/locally-verified로 승급.
- docker-compose.yml 작성 (keycloak + postgres + spring + nginx) —
planned - Keycloak realm/client 설정 + JSON export —
planned - Spring Boot Resource Server (
/api/meendpoint with@AuthenticationPrincipal Jwt) —planned - vanilla JS SPA (login button → PKCE 생성 → callback → token storage →
/api/me호출) —planned - iss mismatch issue 재현 + 해결 (
KC_HOSTNAME=localhostvskeycloak시연) —planned - refresh_token rotation 시연 (Keycloak
Revoke Refresh Token옵션 toggle) —planned - HTTPS 없는 환경에서 token 노출 demonstration (Wireshark/curl로 헤더 캡처) —
planned - (선택) Caddy reverse proxy로 HTTPS 추가 —
planned
진행 중 메모
작업하며 떠오른 메모. 자유 형식.
AXIS_DRIFT(2026-07-18/branch-spec확인) — hub 가 2026-07-14 에 분류 primary 축을 배치×federation 6패턴 → 인증 아키텍처 4패턴(AP1~AP4) 로 교정(raw/project-notes/keycloak-patterns-overview §2.3)했으나 본 노트 본문은 여전히 Phase 0 의 "P3A" 프레이밍이다. 매핑은 P3A → AP1 + cross-cutting 배포=single-EC2 (실 구현 base). hub §2.3·§8 이 "실제 rename/re-parent 은wiki-doc-author mode=migrate로 점진 수행(자동 mv 금지 — wikilink 영향 검토)"라고 명시하므로 본 회차에서는 본문 재작성 없이 정합 표기만 추가(제목 blockquote + 본 메모). 물리parent_branch:는 아직feature-keycloak-patterns이고, AP 그룹 소속은 hub §8 분해표가 SSOT.- 본 노트는 단일-EC2 실 구현 base — detail 의 owner 는 6 자식이다. hub F5 가 "실 구현 배포 = single-EC2 1벌" 로 고정하여 본 노트가 그 물리 base 이지만, 실행 단계(docker-compose / realm / SPA / iss / refresh / Spring RS)는 자식 6개가 각각 owner 다(§Cluster). 따라서 본 노트의
D2·D4·D5는 자식 owner 결정의 요약이라rules/consistency-contract.md의RESTATED_FOREIGN_DECISION소지가 있다 — 사용자 작성 결정을 덮어쓰지 않고 §구현 가이드 §3 에 위임 맵을 세워 포인터를 명시한다. DECISION_DRIFT해소 (2026-07-18) — 과거 parent D4의 manual-first 문구는 historical/superseded다. 실제 코딩 순서는 owner raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce D1의oidc-client-tslibrary-first이며, manualcrypto.subtlePKCE는 baseline E2E 뒤의 비교 학습 단계다.OWNER_SPLIT(Spring RS +aud검증) — 2026-07-18 — 본 §Cluster 는 raw/branch-notes/feature-keycloak-spring-rs-role-mapping 을 나열하나, hub §8 dup-reconciliation 은 "Spring RS 셋업·aud검증 = raw/branch-notes/feature-keycloak-spring-rs-audience-validator 가 owner, role-mapping 의 role→RBAC 부분만 deferred authZ" 로 정했다. §구현 가이드 §3 위임 맵은 두 owner 를 분리 지정한다(RS 셋업/aud = audience-validator, 본 노트 Cluster 의 role-mapping 은 role→RBAC deferred).- repo 부재 (
NO_GROUND_TRUTHfor impl) — 2026-07-18 확인 —/home/donghyeon/workspace/keycloak-patterns/디렉터리가 아직 없다. 따라서 모든 TODO/구현 계획항목은planned(코드로 확인된actually-implemented아님). 계약 근거는 official docs(Keycloak/Spring/OWASP/RFC)이며, 포트 값 등 배포 상수는 자식 raw/branch-notes/feature-keycloak-docker-compose-stack D6 이 owner (KC-CONTAINER-C5 가 "포트 값은 공식 raw verbatim 부재" 로 못박음 → 본 노트에서 official 로 단정 금지). TOPOLOGY_DRIFT(2026-07-18 depth 감사) — 본 노트 §컴포넌트 다이어그램·§구현 가이드 §1 은 3-포트 직노출(nginx=static only, backend/Keycloak 각자 포트)로 토폴로지를 확정하나, hub raw/project-notes/keycloak-patterns-overview §3-1-5 다이어그램은 nginxproxy_pass리버스프록시(same-origin)를 그린다. 본 노트가 토폴로지 owner(D3 · hub F5)이므로 divergence 를 공개만 하고 3-포트를 학습 기본으로 유지 — cross-origin 귀결(CORS / Web Origins)은 §구현 가이드 §1 + §엣지·실패·의존 에서 종결한다.
컴포넌트 다이어그램
EC2 (단일 호스트)
├─ nginx (port 80) → /index.html (vanilla JS SPA static 파일)
├─ Spring Boot (port 8081) → /api/* (Resource Server)
└─ Keycloak (port 8080) → /realms/<realm>/...
Browser → EC2:80 → SPA load
Browser → EC2:8080 → Keycloak (OIDC redirect: /auth → 로그인 → /callback)
Browser → EC2:8081 → Backend (Authorization: Bearer <access_token>)
Backend → EC2:8080/realms/<realm>/protocol/openid-connect/certs (JWKS, localhost network)
신뢰 경계: 단일 호스트 내 localhost trust. 외부에서는 EC2 public IP / DNS만 노출.
토큰 교환 sequence (P2A와 동일 + localhost 특이점)
- SPA: PKCE 생성 —
code_verifier(랜덤 43–128 char),code_challenge = BASE64URL(SHA256(code_verifier)),code_challenge_method=S256. verifier는sessionStorage저장 (단일 auth 라운드트립 수명 — 콜백 직후 폐기하므로 D2/OWASP 의 장기 토큰 저장 금지와는 별개다. 단sessionStorage자체는 XSS 노출면이라raw/official-docs/oauth2-pkce-rfc-7636.mdUsage Boundaries 가 별도 플래그). - SPA → Keycloak
/authredirect — query:client_id,redirect_uri,response_type=code,scope=openid,state,code_challenge,code_challenge_method=S256. - 사용자 로그인 → Keycloak →
redirect_uricallback with?code=...&state=.... - SPA → Keycloak
/token(POST form) —grant_type=authorization_code,code,redirect_uri,client_id,code_verifier. 응답:access_token/refresh_token/id_token/expires_in. - SPA → Backend
Authorization: Bearer <access_token>. - Backend → Keycloak JWKS (
localhost:8080/realms/<realm>/protocol/openid-connect/certs) → public key fetch (캐시) → JWT signature verify +issclaim 검증.
단일 호스트 특이점
KC_HOSTNAME 함정 (이 패턴의 핵심 학습 포인트)
issclaim은 Keycloak이 발급한 JWT 안에 박힘. 예:iss=http://localhost:8080/realms/keycloak-patterns.- Browser는
localhost:8080으로 Keycloak에 접근, backend도 같은 hostname을 issuer-uri로 등록해야 검증 통과. - Docker Compose에서 backend가
keycloak:8080(컨테이너 DNS)로 JWKS를 부르면 issuer mismatch 발생 (token에 박힌iss는localhost:8080인데 backend가 기대하는 issuer가keycloak:8080). - 해결: backend
issuer-uri = http://localhost:8080/realms/...로 통일. JWKS도 같은 hostname으로 부르려면 컨테이너에서 호스트 네트워크 공유(network_mode: host) 또는extra_hosts: [host.docker.internal:host-gateway]후host.docker.internal사용. - 또는 Keycloak
KC_HOSTNAME_BACKCHANNEL_DYNAMIC=true로 frontchannel/backchannel URL 분리 (Keycloak 24+).
redirect_uri mismatch
- Keycloak client 등록 시
Valid redirect URIs정확히 일치해야 함. http://localhost/callback≠http://127.0.0.1/callback≠http://<ec2-public-ip>/callback. 셋 다 다른 URI.- 와일드카드
http://localhost/*허용은 학습 환경 한정. prod 금지.
기타
- PKCE는 여전히 필수 — public client (브라우저는 client_secret 보관 불가).
- HTTPS 없으면 token 평문 노출 —
access_token,refresh_token이 HTTP 헤더/응답으로 평문 전송. 학습 환경 한정. - nginx는 단순 static 파일 서빙 (Caddy 또는 nginx + Let's Encrypt로 HTTPS termination 추가 가능).
장점 / 단점
장점
- 단일 호스트라 네트워크 디버깅 쉬움. tcpdump /
docker compose logs로 한 화면에서 추적. - docker-compose 한 줄로 환경 reset (
docker compose down -v && up). - 학습 곡선 평탄. k8s / ingress / Traefik 등 부가 인프라 없음.
- localhost trust로 보안 변수 최소화 — 외부 노출은 80/8080/8081 세 포트만.
단점
- 운영 환경 모방 X. 실 운영은 Keycloak / API / static 분리 배치(P1·P2 패턴) — 본 패턴은 학습 전용.
- HTTPS termination 별도 처리 필요. Caddy reverse proxy를 앞단에 두거나 nginx에 cert 추가.
- 단일 EC2 장애 = 전체 다운. SPOF.
KC_HOSTNAME설정 잘못 시 디버깅 난이도 급증 (issuer/redirect/JWKS URL 3가지가 얽힘).
결정 사항 (decisions)
- 2026-05-25: HTTPS 없이 진행 (학습 환경). prod 진입 시 Caddy 또는 nginx + Let's Encrypt 추가. 이유: cert 발급/갱신 흐름이 본 학습 주제(OIDC)와 무관.
- 2026-05-25: pure SPA의 access/refresh token은 모두 memory-only로 둔다. reload 시 복원하지 않고 재인증한다. HttpOnly refresh cookie는 TMB/BFF variant이며 본 AP1 baseline이 아니다.
- 2026-05-25 (owner 위임): issuer identity와 JWKS network address의 실행 wiring은 raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch D6을 따른다. 본 base는
KC_HOSTNAME함정의 배포 축만 소유한다. - 2026-05-25 (historical, superseded):
vanilla JS는 manual fetch +.crypto.subtle기반 PKCE 구현 우선 - 2026-07-18: 실제 코딩 순서는 raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce D1의
oidc-client-tslibrary-first다. manual PKCE는 baseline E2E 뒤 비교 학습 단계다. - 2026-05-25: realm 1개 + client 1개 (
spa-client, public, Standard Flow + PKCE S256 강제). multi-tenant / role mapping은 out of scope.
결정-근거 매핑
각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시. P3A 는 학습 환경 단순화 결정 다수 → 일부는
UNSUPPORTED_DECISION(공식 근거 없이 학습 우선순위 기반).
선택 조건열(R2, 2026-07-18 추가): "이 조건일 때 이 결정, 다른 조건이면 어떤 대안". 분기 없으면N/A.Ownership note — 본 노트는 단일-EC2 배포 base 다.
D1·D3은 본 branch 고유(HTTPS 경계 · KC_HOSTNAME 배포 축)이고,D2·D4·D5는 자식 owner 결정의 요약이다(§구현 가이드 §3 위임 맵 · §진행 중 메모RESTATED_FOREIGN_DECISION). 세부는 owner 를 정본으로 본다.
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|---|
| D1 | 학습 환경에서 HTTPS 없이 진행 (Caddy / Let's Encrypt 는 prod 진입 시 추가) | 학습 환경(localhost / 단일 EC2)에서 OIDC lifecycle 자체가 학습 목표일 때만 HTTP. 외부 노출·prod 진입 시 HTTPS edge termination 필수(KC-RP-C4). 또한 frontchannel 이 HTTPS 여야만 브라우저 crypto.subtle(S256 계산)이 secure context 로 동작 — localhost 예외에만 HTTP 허용(§구현 가이드 §2 UNSUPPORTED_IMPL_DECISION) |
UNSUPPORTED_DECISION — 공식 문서는 prod 에서 HTTPS edge termination 을 권고 (raw/official-docs/keycloak-reverseproxy-official.md#KC-RP-C4) 이고, 학습 환경에서 HTTP 만으로 OIDC 를 진행하라는 권고는 어느 공식 자료에도 없음 |
(학습 우선순위 기반 결정) | HTTP 위에서 토큰이 평문 전송 → 학습 환경 외 노출 시 즉시 노출. KC-CONTAINER-C3 (start-dev insecure defaults) 와 결합 시 prod 절대 금지 |
| D2 | pure SPA의 access/refresh token을 모두 memory-only로 보관하고 reload 시 재인증 | AP1 pure SPA baseline이면 memory-only. 세션 지속이 요구되면 HttpOnly refresh cookie를 슬쩍 추가하지 않고 AP2(TMB) 또는 AP3(BFF) variant로 전환한다. owner = raw/branch-notes/feature-keycloak-spa-token-storage-tradeoff D1, 구현 consumer = raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce D2 | raw/official-docs/owasp-html5-storage-xss-spa.md#OWASP-HTML5-C1, raw/official-docs/owasp-html5-storage-xss-spa.md#OWASP-HTML5-C2, raw/official-docs/owasp-html5-storage-xss-spa.md#OWASP-HTML5-C4 |
official-reference (owner를 통한 위임) |
memory token도 실행 중 XSS에 노출된다. reload UX를 허용할 수 없으면 별도 server-side custody·CSRF 계약을 갖는 variant가 필요 |
| D3 | KC_HOSTNAME이 정하는 issuer identity와 backend의 JWKS 도달성을 함께 맞춘다 |
실행 profile의 정확한 값과 network mechanism은 raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch D6이 owner다. 본 base는 단일-host에서 두 조건이 모두 필요하다는 배포 requirement만 소유 | raw/official-docs/keycloak-hostname-configuration.md#KC-HOST-C2, raw/official-docs/keycloak-hostname-configuration.md#KC-HOST-C3, raw/official-docs/spring-security-resource-server-jwt.md#SSRS-JWT-C1, raw/official-docs/spring-security-resource-server-jwt.md#SSRS-JWT-C2 |
official-vendor-doc + delegated |
실제 Docker profile은 owner D6의 401→200 E2E 전까지 planned |
| D4 | 실제 코딩 순서 = oidc-client-ts 우선, manual PKCE는 비교 학습용 별도 단계 |
baseline E2E를 먼저 확보할 때 library-first. 내부 알고리즘 비교는 이후 manual 단계. 실행 owner = raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce D1 | owner D1의 OIDCTS-C2/C3 + raw/official-docs/oauth2-pkce-rfc-7636.md#PKCE-RFC7636-C2, #PKCE-RFC7636-C3 |
delegated official-vendor-doc + official-standard |
manual 단계의 crypto.subtle secure-context 동작은 별도 확인 필요 |
| D5 | realm 1개 + client 1개 (spa-client, public, Standard Flow + PKCE S256 강제) |
단일 패턴 학습이면 realm 1 / client 1(spa-client public). hub F3 대로 4 패턴 통합 base 로 확장 시 realm 은 공유 1개 유지, client 는 패턴당 1개(spa-public / token-mediating-confidential / bff-confidential / edge-proxy)로 분리 — aud 로 client 구분. multi-tenant / role mapping 은 out of scope. owner = raw/branch-notes/feature-keycloak-realm-client-export D1 (public+PKCE S256) |
raw/official-docs/oauth2-pkce-rfc-7636.md#PKCE-RFC7636-C1, raw/official-docs/oauth2-pkce-rfc-7636.md#PKCE-RFC7636-C3, raw/official-docs/keycloak-getting-started-docker.md#KC-GSD-C3, raw/official-docs/keycloak-getting-started-docker.md#KC-GSD-C4 |
official-standard + official-vendor-doc |
Keycloak Admin UI 에서 PKCE S256 강제 옵션의 정확한 토글명/위치는 공식 quickstart 인용에 없음 — Admin UI 실 확인 필요 |
구현 가이드
본 branch 는 단일-EC2 배포 통합 base (hub F5) — 실행 코드가 아니라 (1) 물리 토폴로지 경계 명세, (2)
KC_HOSTNAME/redirect_uri배포측 signature 함정, (3) 6 자식 owner 로의 위임 맵이 산출물이다. 아래 sub-section 은 본 branch 고유 결정(D1·D3·D5)에서만 도출하며, 자식이 owner 인 실행 detail 은 재진술하지 않고 포인터로 위임한다(rules/consistency-contract.mdReference-Only). 실 구현 등급은 모두planned(repo 부재 — §진행 중 메모NO_GROUND_TRUTH).
1. 단일 EC2 토폴로지 경계 — 누가 어디서 무엇을 하는가
Trace: D3 (KC_HOSTNAME 통일 —
KC-HOST-C2/KC-HOST-C3), D5 (realm/client —PKCE-RFC7636-C1/KC-GSD-C3), hub F5 (single-EC2 배포 base). 본 §가 §컴포넌트 다이어그램을 결정-trace 로 종결한다.
- UNSUPPORTED_IMPL_DECISION: 포트 값(80/8080/8081)·네트워크 메커니즘·컨테이너명은 어느 official 인용도 강제하지 않는다(
KC-CONTAINER-C5가 "포트/env 값은 공식 raw verbatim 부재" 로 명시). owner 는 자식 raw/branch-notes/feature-keycloak-docker-compose-stack D6 — 본 §는 경계와 노출 정책만 확정하고 상수는 위임한다.- TOPOLOGY / CORS 귀결 (2026-07-18 depth 감사 반영): 본 §가 확정한 3-포트 직노출(nginx=static only)은 브라우저에 2개의 cross-origin 레그를 만든다 — SPA(:80)→Keycloak(:8080)
/token+ SPA(:80)→backend(:8081)/api+Authorization. 따라서 Keycloak Web Origins(KC-GSD-C4— "Set Web origins to ...") + backend Spring CORS 가 필수다(§엣지·실패·의존 CORS 경로 + §3 위임 맵). ⚠️TOPOLOGY_DRIFT: hub §3-1-5 다이어그램은 nginxproxy_pass리버스프록시(same-origin)를 그리나 본 노트는 3-포트 직노출을 그린다(§진행 중 메모TOPOLOGY_DRIFT). 본 노트가 토폴로지 owner(D3 · hub F5)이므로 학습-최소 3-포트를 기본으로 두되,proxy_pass채택 시/api는 same-origin 화되어 Spring CORS 가 소거된다 — 그래도 SPA→Keycloak/token은 여전히 cross-origin 이라 Web Origins 는 토폴로지와 무관하게 필수.
| 컴포넌트 | 역할 (무엇을 보유 / 수행) | 외부 노출 | 근거 | 등급 |
|---|---|---|---|---|
| nginx | vanilla JS SPA static 서빙 (public client) | :80 (외부) |
§컴포넌트 다이어그램; 포트 상수는 child raw/branch-notes/feature-keycloak-docker-compose-stack D6 | planned |
| Spring Boot (Resource Server) | 토큰 미보유 — 요청마다 JWT를 검증하고 audience 계약을 적용 | :8081 (외부) |
raw/branch-notes/feature-keycloak-spring-rs-audience-validator D1/D6 | planned |
| Keycloak (Authorization Server) | 토큰 발급 + JWKS publish, KC_HOSTNAME 로 issuer hostname 고정 |
:8080 (외부) |
KC-CONTAINER-C1 (KC_HOSTNAME=노출 주소), KC-HOST-C2; realm 모델 KC-GSD-C3 |
planned |
| PostgreSQL | Keycloak realm/user persistence | 내부 only (미노출) | child raw/branch-notes/feature-keycloak-docker-compose-stack D2 (dev-file 대신 postgres) | planned |
| trust 경계 | 단일 host localhost trust — 외부는 위 3 포트만, backend↔Keycloak JWKS 는 loopback | — | D3 (localhost 통일); §컴포넌트 다이어그램 신뢰 경계 | planned |
2. 배포측 signature 함정 — KC_HOSTNAME(iss) + redirect_uri
Trace: D3 (
KC-HOST-C2hostname 의무·dynamic resolution 차단,KC-HOST-C3fraudulent issuer 방어), D5 (KC-GSD-C4Valid redirect URIs 설정). 본 §는 단일 host 배포에서만 발생하는 고유 관심사이며 §단일 호스트 특이점을 결정-trace 로 종결한다.
- issuer/JWKS의 실행 profile은 child raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch D6이 owner다. 본 §는 함정의 존재·재현 조건·해결 축만 확정하고 구체 mechanism을 복제하지 않는다.
- UNSUPPORTED_IMPL_DECISION: 브라우저
crypto.subtle(S256 계산)은 secure context 에서만 노출되어 non-localhostHTTP origin 에선 차단된다. trade-off: 학습 환경의localhost예외에 의존해 HTTP 를 쓰되(D1), 그 외에는 frontchannel = HTTPS 로 둔다 — 본 corpus 에 이 브라우저 제약의 직접 인용 없음(oauth2-pkce-rfc-7636.mdUsage Boundaries 도 "추가 확인 필요"로만 기록, §Claims To Verify).
| 함정 | 발생 (재현 조건) | 기대 동작 / 해결 | 근거 | owner (실행) |
|---|---|---|---|---|
| iss mismatch | browser 는 토큰의 iss=http://localhost:8080/realms/... 를 받고, backend 가 keycloak:8080(컨테이너 DNS)을 기대 issuer 로 설정 → 모든 요청 401 |
KC_HOSTNAME=localhost 통일 + backend issuer-uri 동일 값 + loopback 도달 메커니즘 |
KC-HOST-C2, KC-HOST-C3, SSRS-JWT-C1 |
재현·해결 → raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch D1/D4/D6 |
| redirect_uri mismatch | http://localhost/callback ≠ http://127.0.0.1/callback ≠ http://<public-ip>/callback — scheme·host·port·trailing slash 한 글자만 달라도 authorization request 거부 |
등록 URI 와 접근 hostname 1:1 일치 또는 둘 다 등록. wildcard /* 는 학습 한정 |
KC-GSD-C4 (Valid redirect URIs 설정 — vendor). exact-match MUST 표준(OA21-C5)은 본 노트 Sources 밖 → raw/branch-notes/feature-keycloak-internal-spa-direct-no-google D7 이 owner-absent 로 흡수(Reference-Only) |
wildcard 정책 → raw/branch-notes/feature-keycloak-realm-client-export D2; 실 redirect_uri 값 → raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce D5 (callback.html) |
| crypto.subtle secure context | non-localhost HTTP origin 에서 crypto.subtle.digest('SHA-256', ...) 차단 → S256 challenge 계산 불가 → PKCE 흐름 실패 |
localhost 학습만 HTTP 허용, 그 외 frontchannel = HTTPS |
UNSUPPORTED_IMPL_DECISION (본 corpus 직접 인용 없음, §Claims To Verify) |
raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce |
3. 결정 위임 맵 (6 자식 owner — Reference-Only)
Trace: D2·D4·D5 는 본 base 가 요약만 보유하고 실행 detail 의 owner 는 자식이다(§진행 중 메모
RESTATED_FOREIGN_DECISION).rules/consistency-contract.mdSingle-Owner 에 따라 세부는 owner 를 정본으로 본다 — 본 표는 포인터 + 1줄 요약만 유지하고 임계값·메커니즘을 재진술하지 않는다.
- UNSUPPORTED_IMPL_DECISION: 없음 — 각 행은 owner 노트의 실존
D<n>을 가리킨다(2026-07-18 확인).
| 관심사 | owner (정본) | owner 결정 | 1줄 요약 (본 base 의 인용) |
|---|---|---|---|
| docker-compose 스택 (keycloak+postgres+nginx+spring, healthcheck, realm auto-import) | raw/branch-notes/feature-keycloak-docker-compose-stack D1~D6 | start-dev + postgres + depends_on: service_healthy + --import-realm + KC_HOSTNAME=localhost + 포트/.env secret |
§컴포넌트 다이어그램·§구현 계획의 docker-compose 항목 정본 |
| realm/client 설정 + JSON export | raw/branch-notes/feature-keycloak-realm-client-export D1~D5 | public + PKCE S256, redirect wildcard(학습), rotation 값, AT 5분, export+redact commit | 본 base D5 요약의 정본 |
| vanilla JS SPA PKCE 코드 | raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce D1~D5 | library-first baseline, manual은 비교 학습 단계 | 본 base D4와 정렬 완료 |
| iss 함정 재현·해결 | raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch D1/D4/D6 | issuer identity/JWKS reachability 실행 profile | 본 base D3·§구현 가이드 §2 의 재현·해결 위임 |
| refresh rotation + logout | raw/branch-notes/feature-keycloak-refresh-rotation-and-logout D1/D2/D4/D5 | rotation ON + Max Reuse 0, AT 5분, revoke/logout 분리, rotation flow 시연 | 본 base D2(refresh 저장) 인접 — rotation 정책 정본 |
Spring RS + aud validator |
raw/branch-notes/feature-keycloak-spring-rs-audience-validator D1/D6 | RS 공통 셋업과 단일 backend audience 검증 | 본 base 백엔드 authN 검증 owner |
| role→RBAC (deferred) | raw/branch-notes/feature-keycloak-spring-rs-role-mapping D4~D6 | realm/client role을 Spring authority로 변환·강제 | 4패턴 authN E2E 이후 착수 |
| CORS 경계 (Keycloak Web Origins + Spring CORS) | Web Origins → raw/branch-notes/feature-keycloak-realm-client-export · Spring CORS → SecurityFilterChain (RS 셋업 owner = raw/branch-notes/feature-keycloak-spring-rs-audience-validator per OWNER_SPLIT) |
Web Origins 에 SPA origin 등록(KC-GSD-C4) + Spring CORS 로 SPA origin whitelist |
3-포트 직노출의 cross-origin 귀결(§구현 가이드 §1 · §엣지·실패·의존) — 본 base 는 경계·owner 만 지정, 값은 owner |
엣지·실패·의존
R4(깊이 게이트) 캡처용. 본 base 는
planned(repo 부재) 이나, 단일-EC2 스택을 실제로 세울 때 부딪힐 실패/엣지와 다른 계약 의존을 미리 열거한다.
- 실패·엣지 경로:
issmismatch (본 배포의 대표 함정): browser 는 frontchannel hostname 으로 토큰을 받고 backend 가 컨테이너 DNS(keycloak:8080)를 기대 issuer 로 설정하면 전 요청401. 기대 동작:KC_HOSTNAME=localhost통일 + backendissuer-uri동일(D3). 재현·해결과 실행 profile은 raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch D1/D4/D6에 위임한다.redirect_urimismatch → 인증 시작 단계에서 거부:iss를 맞춰도 그 앞에서 깨지는 경로.localhostvs127.0.0.1vs public-ip 한 글자만 달라도 authorization request 거부되고 토큰 교환까지 가지 못함. 기대 동작: 등록/접근 hostname 1:1 (§구현 가이드 §2). 근거:KC-GSD-C4(vendor) + exact-match MUST 는 raw/branch-notes/feature-keycloak-internal-spa-direct-no-google D7(OA21-C5) 참조. owner: realm wildcard 정책 raw/branch-notes/feature-keycloak-realm-client-export D2 + 실 redirect_uri raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce D5.crypto.subtle차단 (non-localhost HTTP): S256 challenge 계산 불가 → PKCE 흐름 실패. 기대 동작:localhost학습만 HTTP, 그 외 HTTPS(D1).UNSUPPORTED_IMPL_DECISION— corpus 직접 인용 없음(§Claims To Verify).- CORS preflight 실패 (본 base 3-포트 직노출의 귀결): SPA(:80)→Keycloak(:8080)
/token과 SPA(:80)→backend(:8081)/api+Authorization은 둘 다 cross-origin → whitelist 없으면 브라우저가 preflight 에서 차단. 기대 동작: Keycloak client Web Origins 에 SPA origin 등록(KC-GSD-C4— "Set Web origins to ...") + backend Spring CORS 로 SPA origin 만 허용.UNSUPPORTED_IMPL_DECISION(Spring CORS 측): 본 Sources 에 Spring CORS 직접 인용 없음 — 일반 브라우저 동작 원리. 위임: Web Origins → raw/branch-notes/feature-keycloak-realm-client-export, Spring CORS → SecurityFilterChain owner raw/branch-notes/feature-keycloak-spring-rs-audience-validator (RS 셋업,OWNER_SPLIT). nginxproxy_passsame-origin 화 시/apiCORS 는 소거되나/tokenWeb Origins 는 잔존(§구현 가이드 §1TOPOLOGY_DRIFT). - HTTPS 부재 → 토큰 평문 노출:
access_token/refresh_token이 HTTP 헤더/응답으로 평문 전송(D1). 학습 한정, 외부 노출 시 즉시 위험.KC-CONTAINER-C3(start-devinsecure defaults)와 결합 시 prod 절대 금지. - refresh token 재사용 탐지: rotation ON 상태에서 탈취자와 정상 사용자가 같은 refresh 를 쓰면 token family 전체 무효 → 침해 시그널이자 정상 사용자 강제 로그아웃(가용성 비용). 기대 동작: 재로그인 유도. 위임: raw/branch-notes/feature-keycloak-refresh-rotation-and-logout D1/D5.
aud미검증 → cross-client token reuse: 같은 realm 타 client 토큰이 통과할 수 있다. 기대 동작과 구현 방식은 raw/branch-notes/feature-keycloak-spring-rs-audience-validator D1을 따른다.- Keycloak 미가용: backend 는 JWKS 를 캐시하므로 이미 발급된 토큰은 캐시 유효 동안 계속 검증되나 신규 로그인은 즉시 차단. 캐시 TTL 수치는 미검증(§Claims To Verify).
- SPOF — 단일 EC2 다운 = 전체 정지(§장점/단점). 운영급은 P2A cluster + Keycloak HA(hub §5 deferred).
- 다른 계약 의존:
- raw/project-notes/keycloak-patterns-overview §5 고정 결정 F5(실 구현 배포 = single-EC2 1벌) — 본 노트가 그 물리 base. F5 가 바뀌어 cluster/edge E2E 가 요구되면 배포 전략 재설계.
- raw/project-notes/keycloak-patterns-overview §2 고정 결정 F1(taxonomy AP1~AP4) — 본 노트 = AP1 + 배포=single-EC2. branch 재정의 금지(SSOT 는 hub).
- raw/project-notes/keycloak-patterns-overview §5 고정 결정 F3(단일 realm + 패턴당 client 1개) — D5 의 확장 형태. 4 패턴 base 로 쓸 때 client 분리로
aud구분. - raw/project-notes/keycloak-patterns-overview §5 고정 결정 F4(confidential secret = env var, 미커밋) — AP2/AP3 를 이 base 에 얹을 때
.env/KC_*주입, realm export 평문 금지. - raw/branch-notes/feature-keycloak-docker-compose-stack D1~D6 — 본 base 스택의 정본. 포트/network/import 가 바뀌면 §컴포넌트 다이어그램 갱신.
- raw/branch-notes/feature-keycloak-realm-client-export D1~D5 — D5 요약이 의존.
- raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce D1~D5 — D4와 정렬된 실행 owner.
- raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch D1/D4/D6 — D3 의 재현·해결 위임.
- raw/branch-notes/feature-keycloak-refresh-rotation-and-logout D1/D5 — D2 인접.
- raw/branch-notes/feature-keycloak-spring-rs-audience-validator D1/D6 — RS 셋업·audience owner. raw/branch-notes/feature-keycloak-spring-rs-role-mapping D4~D6 — deferred RBAC owner.
- sibling raw/branch-notes/feature-keycloak-internal-spa-direct-no-google (P2A) D1 — 토큰 교환 흐름 동형(본 §토큰 교환 sequence 가 "P2A와 동일" 선언). AP1 pattern hub 는 P2A.
- sibling raw/branch-notes/feature-keycloak-single-ec2-google-federation (P3B) — Google 변형. 본 no-google base 에 brokering 을 코드 0줄로 얹음.
검증해야 할 주장
공식 문서는 근거지만, P3A 학습 환경에서의 실제 동작은 별도 검증 필요.
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
KC_HOSTNAME=localhost 가 docker-compose 컨테이너 내부에서 의도대로 작동 (token iss=http://localhost:8080/realms/...) |
공식 hostname guide 는 localhost 사용 권고가 dev/quickstart 한정. 학습 환경에서 host 네트워크 의존성이 컨테이너 격리와 충돌 가능 |
docker compose up -d 후 access token 발급 → jwt.io 또는 jq 로 iss claim 확인 |
needs-confirmation |
network_mode: host 가 Linux EC2 에서 정상 동작 (Docker Desktop 가정 제약 회피) |
Linux 호스트는 host 네트워크 지원, mac/Windows Docker Desktop 은 제약. 학습 환경이 EC2 Linux 인지 로컬 Docker Desktop 인지에 따라 결과 다름 | EC2 ubuntu 에서 docker compose ps + curl http://localhost:8080/realms/keycloak-patterns/.well-known/openid-configuration 확인 |
planned |
backend (spring-boot-starter-oauth2-resource-server) 가 issuer-uri=http://localhost:8080/... 로 startup 시 JWKS discovery 성공 |
SSRS-JWT-C2 는 4단계 discovery 를 보장하지만 컨테이너 → 호스트 loopback 도달성은 별도 |
backend 로그에서 JwtDecoder 초기화 메시지 + /api/me 호출 결과 확인 |
planned |
crypto.subtle.digest('SHA-256', ...) 가 학습 환경 (http://localhost) Secure Context 예외로 사용 가능 |
일반적 HTTP origin 은 Secure Context 아님 → SubtleCrypto 차단. localhost 는 브라우저 vendor 별 예외 처리 | Chrome/Firefox 에서 app.js 콘솔에 await crypto.subtle.digest(...) 호출 확인 |
needs-confirmation |
redirect_uri=http://localhost/callback 등록 후 http://127.0.0.1/callback 으로 callback 시 Keycloak 이 거부 (의도된 mismatch 시연) |
OA21-C5 exact-match 표준은 있으나 Keycloak 의 실제 enforce 동작 (대소문자, trailing slash, host 동등성) 은 별도 |
Admin UI 에서 valid redirect URIs 등록 후 hostname 변형 시 redirect_uri_mismatch 에러 확인 |
planned |
Keycloak Revoke Refresh Token: ON + Max Reuse: 0 토글이 refresh rotation 을 실제로 한 번만 허용 |
공식 인용 부재 (oauth2.1 OA21-C3 는 scope/resource binding 만 언급) |
refresh token 두 번 연속 사용 → 두 번째 호출에서 4xx 응답 확인 | planned |
마주친 문제
- (구현 시작 후 추가)
KC_HOSTNAME설정 misconfiguration으로 인한 issuer mismatch 예상. - (구현 시작 후 추가)
redirect_uri등록 시localhostvs127.0.0.1혼동 예상.
구현 계획
- Repo 위치:
/home/donghyeon/workspace/keycloak-patterns/(별도 git repo, LLM Wiki 외부). ⚠️ 2026-07-18 현재 미존재 — §진행 중 메모NO_GROUND_TRUTH. 아래 전부planned. - docker-compose.yml:
keycloak(quay.io/keycloak/keycloak:26.x,start-dev,KC_HOSTNAME=localhost,KC_HTTP_ENABLED=true,KC_BOOTSTRAP_ADMIN_USERNAME=admin)postgres(Keycloak realm persistence, volume mount)backend(Spring Boot 3 + Java 21,spring-boot-starter-oauth2-resource-server)nginx(static SPA serve, port 80)- (선택)
caddyreverse proxy for HTTPS - (실행 detail 정본: raw/branch-notes/feature-keycloak-docker-compose-stack D1~D6)
- SPA:
index.html+app.js— raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce D1에 따라oidc-client-ts로 baseline E2E를 먼저 만들고 manual PKCE는 비교 단계에서 수행한다. - Backend:
- Spring Boot 3 + Java 21
application.yml:spring.security.oauth2.resourceserver.jwt.issuer-uri: http://localhost:8080/realms/keycloak-patterns/api/meendpoint with@AuthenticationPrincipal Jwt→ returnjwt.getClaims().- (실행 detail 정본: raw/branch-notes/feature-keycloak-spring-rs-audience-validator D1/D6; RBAC는 raw/branch-notes/feature-keycloak-spring-rs-role-mapping D4~D6)
- Keycloak realm export JSON:
keycloak-patterns-realm.json(realm + client + 테스트 사용자) commit. (실행 detail 정본: raw/branch-notes/feature-keycloak-realm-client-export D1~D5)
관련
- 부모 root: raw/branch-notes/feature-keycloak-patterns
- 동형 (token flow 동일): raw/branch-notes/feature-keycloak-internal-spa-direct-no-google (P2A Internal SPA + Resource Server, no Google)
- 다음 패턴: raw/branch-notes/feature-keycloak-single-ec2-google-federation (P3B Single EC2 + Google federation)
묶음 (자식 sub-sub-branches — P3A 실 구현 6단계)
- raw/official-docs/keycloak-getting-started-docker
- raw/official-docs/keycloak-hostname-configuration
- raw/official-docs/keycloak-server-containers-docker
- raw/official-docs/oauth2-pkce-rfc-7636
- raw/official-docs/oidc-client-ts-library
- raw/official-docs/spring-security-resource-server-jwt
- raw/branch-notes/feature-keycloak-docker-compose-stack
- raw/branch-notes/feature-keycloak-realm-client-export
- raw/branch-notes/feature-keycloak-spring-rs-role-mapping
- raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce
- raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch
- raw/branch-notes/feature-keycloak-refresh-rotation-and-logout
Sources는 상단 "외부 근거" 섹션. Errors/Interview/Lectures/Blog 는 현재 없음 — P3A 실 구현(Phase 3) 진입 시 errors / interview-prep 등재 예상.
관련 일일 노트
완료 후 정리
- PR 링크: (별도 keycloak-patterns repo)
- 리뷰 메모:
- 머지 결과 / 배포 환경: 단일 EC2(또는 로컬 docker-compose) 시뮬레이션, 로컬 검증까지.
- wiki 추출 대상 (verified만,
wiki/projects/로만 추출):actually-implemented항목: (구현 후 채움)locally-verified항목: (구현 후 채움)prod-verified항목: (없음, prod 배포 out of scope)
- 추출하지 않을 항목 (planned / documented-only / abandoned): 현재 전부
planned. 구현 완료된 부분만 wiki/projects/로 승급.