Files
llm-wiki/raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch.md
T

49 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-iss-claim-hostname-mismatch (iss claim mismatch 함정 + KC_HOSTNAME 해결) branch-note raw BR-KEYCLOAK-PATTERNS-OVERVIEW-005 project-work-item keycloak-patterns-overview WI-KEYCLOAK-PATTERNS-OVERVIEW-005
DEC-KEYCLOAK-PATTERNS-OVERVIEW-SPA-CLIENT-001@1
DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1
WI-KEYCLOAK-PATTERNS-OVERVIEW-003
1 feature-keycloak-iss-claim-hostname-mismatch
keycloak-patterns
branch
keycloak-patterns
p3a
implementation
kc-hostname
iss-mismatch
troubleshooting
2026-05-25 in-progress a528d5f258776792a6803ccea4e7946bc7905bbc6abee798b12b4c68a9afda66

branch: feature-keycloak-iss-claim-hostname-mismatch (iss claim mismatch 함정 + KC_HOSTNAME 해결)

Layer: raw/branch-notes/raw/project-notes/keycloak-patterns-overviewWI-KEYCLOAK-PATTERNS-OVERVIEW-005 직접 branch. P3A는 실 구현 대상. 본 sub-sub는 학습 + 작업 plan 기록 — 실 구현은 /home/donghyeon/workspace/keycloak-patterns/. ⚠️ NO_GROUND_TRUTH (2026-07-17 확인): /home/donghyeon/workspace/keycloak-patterns/디스크에 존재하지 않는다. raw/project-notes/keycloak-patterns-overview §9 도 "아직 비어 있음 — Phase 2 진입 시 생성" 으로 기록. 따라서 본 노트의 모든 구현 항목은 planned 이며, 코드로 확인된 actually-implemented0건이다.

부모 (필수)

raw/project-notes/keycloak-patterns-overview

브랜치 계약 패킷

  • 생성 시 프로젝트 개정: 1
  • 패킷 스키마: contract_packet: 1
  • 완료 조건: hostname 미설정 iss mismatch 401과 설정 후 복구 log가 존재한다

상속한 프로젝트 결정

Decision Ref Project Summary Branch Application Source
DEC-KEYCLOAK-PATTERNS-OVERVIEW-SPA-CLIENT-001@1 AP1은 public client와 Authorization Code + PKCE를 사용한다 SPA access token의 issuer 검증과 Keycloak hostname wiring에 적용한다 raw/project-notes/keycloak-patterns-overview
DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1 done-bar는 E2E success와 signature security failure 재현·해결 evidence다 iss mismatch 401과 설정 후 복구 log를 완료 evidence로 사용한다 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의 가장 흔한 함정을 의도적으로 재현하고 해결한다. browser는 localhost:8080(또는 EC2 public DNS)으로 Keycloak에 접근하지만, backend는 Docker internal network keycloak:8080을 보면서 JWT의 iss claim이 mismatch — JWT validation 실패. KC_HOSTNAME 설정으로 해결.

면접 질문: "단일 호스트 docker-compose에서 OIDC가 동작 안 했던 경험이 있나요?" → "browser가 보는 issuer identity는 http://localhost:8080으로 고정하고, backend의 issuer-uri도 token iss와 같은 localhost 값을 사용합니다. 실제 JWKS fetch는 jwk-set-uri=http://host.docker.internal:8080/.../certs로 분리합니다. 이 구성은 아직 planned이며, 401→200과 key rotation을 로컬에서 검증해야 합니다."

2026-07-18 /sync 채택 (D6): Docker dev 기본은 issuer identity와 JWKS network address 분리다. issuer-uri=http://localhost:8080/realms/keycloak-patterns, jwk-set-uri=http://host.docker.internal:8080/realms/keycloak-patterns/protocol/openid-connect/certs를 사용하고 backend에 extra_hosts: ["host.docker.internal:host-gateway"]를 둔다. SSRS-JWT-C5에 따라 앞 값은 iss 문자열 검증, 뒤 값은 실제 key fetch를 담당한다. 실제 구현·검증 전까지는 planned이며 과거형 경험으로 표현하지 않는다.

  • 이슈:
  • PR: (별도 keycloak-patterns repo)

범위

포함 범위

  • 의도적 실패 재현 (KC_HOSTNAME 미설정 / KC_HTTP_ENABLED=true만)
  • backend Spring 로그에 JWT issuer mismatch 또는 Could not validate iss 에러 확인
  • KC_HOSTNAME=localhost 설정 후 양쪽 issuer 일치 검증
  • 해결 방안 4가지 비교 (2026-07-17: 기존 3가지 A/B/C 에 F 추가 — 조사 결과 F 가 가장 이식성 높은 해법으로 판정, D6):
    • (A) KC_HOSTNAME=localhost + 컨테이너 간 extra_hosts: [host.docker.internal:host-gateway] → backend가 http://host.docker.internal:8080으로 JWKS 호출
    • (B) network_mode: host (Docker hairpin NAT — Linux only)
    • (C) backend container /etc/hostskeycloak을 host gateway에 매핑 (extra_hosts 응용)
    • (F, 채택) Spring issuer-uri / jwk-set-uri 분리 — issuer-uri는 localhost token iss 검증, jwk-set-urihost.docker.internal을 통한 실제 JWKS fetch. Linux backend에는 extra_hosts: ["host.docker.internal:host-gateway"]를 둔다. 근거 SSRS-JWT-C5 + DOCKER-COMPOSE-NET-C3/C4.
  • EC2 환경에서 KC_HOSTNAME=ec2-xx-xx-xx-xx.compute.amazonaws.com 설정 시 변화 (브라우저가 EC2 public DNS로 접근)
  • frontchannel/backchannel URL 분리 옵션 (KC_HOSTNAME_BACKCHANNEL_DYNAMIC=true, Keycloak 24+)

제외 범위

  • HTTPS / Let's Encrypt cert 발급 (학습 환경 HTTP)
  • EC2 보안그룹 / VPC 세팅
  • Caddy / nginx reverse proxy 앞단 추가
  • aud (audience) claim 검증 — 프로젝트 노트 §중복 정합(2026-07-14) 이 feature-keycloak-spring-rs-audience-validator 를 owner 로 지정. 본 branch 는 iss 만 다룬다.
  • realm / client 생성 및 exportraw/branch-notes/feature-keycloak-realm-client-export 소유.
  • network_mode: "service:<name>" (E) 패턴 — 2026-07-17 조사가 발견한 5번째 대안이나, 더미 anchor 컨테이너 의존 + 포트 관리 비용이 서비스 2개 규모에 과설계라 채택 안 함 (§Audit & Findings A4).

근거 (필수, 최소 1개+)

이 branch의 구현·설계 결정의 근거가 되는 외부 자료. (2026-07-17 정리: 병행 dispatch 로 생긴 중복 항목 제거 + ## Cluster 하위에 잘못 생성된 ### Sources 서브섹션을 본 표로 통합.)

Source 정당화하는 결정
raw/official-docs/keycloak-hostname-configuration D1 — Keycloak hostname guide. hostname 설정 의무(KC-HOST-C2) + fraudulent issuer 방지(KC-HOST-C3) + backchannel 분리 capability(KC-HOST-C1,C4) + hostname-strict 기본 true(KC-HOST-C5)
raw/official-docs/spring-security-resource-server-jwt D6 의 핵심 근거issuer-uri 는 token iss 값이어야 하고 RS 가 이 값으로 self-configure(SSRS-JWT-C1), discovery 4단계 중 4번이 iss 비교(SSRS-JWT-C2), jwk-set-uri 지정 시 discovery 를 하지 않으며 issuer-uriiss 검증용으로만 남음(SSRS-JWT-C5). D5 의 "iss 검증 = 신뢰의 본질" 보조 근거. ⚠️ 2026-07-17 이전까지 이 branch Sources 에 링크되지 않아 D6 를 놓치고 있었음 (§Audit & Findings A1)
raw/official-docs/docker-compose-networking-extra-hosts-official D6 — extra_hosts custom hostname 매핑(DOCKER-COMPOSE-NET-C3) + host-gateway 특수값(C4) + Linux vs Mac/Win 해석차(C5) = 해결 A/C 의 메커니즘 근거. 서비스명 internal DNS 가 별도 설정 없이 도달(C1,C2) = 해결 F 의 도달성 근거
raw/official-docs/docker-host-network-driver-official D6 — 해결 (B) network_mode: host 의 플랫폼 제약. Linux native + Docker Desktop 4.34+ opt-in(DOCKER-HOSTNET-C1,C2), Windows 컨테이너 미지원(C3), ports: 무시(C4), Desktop 은 layer 4 한정(C5)
raw/official-docs/docker-engine-20-10-release-notes-official D6 — host.docker.internal 의 Linux dockerd 지원이 20.10.0(2020-12-08)에서 시작(DOCKER-2010-C1). 본문 "최소 Docker 20.10+" 메모의 부분 confirm 근거
raw/official-docs/keycloak-2500-hostname-v2-release-official D7 — hostname v2 도입(25.0.0) 사유·동작 변경 경고·v1 deprecated(KC-2500-C1~C4). 본문 "옵션 명칭이 자주 바뀜" 메모의 프레이밍 정정 근거
raw/official-docs/keycloak-2600-hostname-v1-removed-official D7 — 26.0.0 에서 hostname v1 완전 제거(KC-2600-C1) + proxy 옵션 제거(C2). 이 프로젝트가 26.x 고정이므로 v2 가 유일 옵션 집합임을 확정
raw/official-docs/openid-connect-core-id-token-validation D5 — "RFC 7519 + OIDC Core spec 모두 iss 검증을 mandatory 로 규정" 진술의 미증명 상태를 해소. §3.1.3.7 item 2 (iss MUST exactly match, OIDC-CORE-C3) verbatim quote 가 직접 근거

TODO

  • 실패 재현: docker-compose.yml에서 KC_HOSTNAME 제거, KC_HTTP_ENABLED=true만 — 등급: planned
  • browser로 http://localhost:8080에서 로그인 → SPA가 token 받음 → /api/me 호출 → backend 401 — 등급: planned
  • backend 로그에서 JwtValidationException / iss claim did not match 메시지 캡처 → screenshot/로그 발췌 — 등급: planned
  • decode된 access token의 iss claim 캡처 (jwt.io 사용) — 등급: planned
  • 해결 (A): KC_HOSTNAME=localhost 설정 + backend extra_hosts: ["host.docker.internal:host-gateway"] + application.yml issuer-uri: http://host.docker.internal:8080/... — 등급: planned
    • 단점: backend가 보는 issuer-uri와 token 안의 iss가 또 mismatch
    • 사실 정답은: token issuer와 backend issuer-uri를 정확히 일치시키는 것
    • (2026-07-17 조사) 이 자기 진단은 정확했다 — A 원안은 iss 불일치로 기능적으로 실패한다. 다만 "정확히 일치" 를 네트워크 도달성까지 같은 URL 로 달성해야 한다는 전제는 SSRS-JWT-C5 기준 틀렸다(F 참조).

  • 해결 (정답 재정의): KC_HOSTNAME=localhost + backend issuer-uri: http://localhost:8080/realms/keycloak-patterns + backend container가 localhost를 host gateway로 매핑 → 컨테이너 내부 localhost:8080이 호스트 8080으로 라우팅 — 등급: planned
  • 해결 (B): network_mode: host 시도 (Linux only) → backend가 host network share → localhost:8080 직접 도달 — 등급: planned
  • 해결 (C): backend container extra_hosts: ["localhost:host-gateway"] 또는 keycloak:host-gateway 후 issuer-uri 정렬 — 등급: planned
  • 해결 (F, 기본): KC_HOSTNAME=localhost 유지 + backend issuer-uri: http://localhost:8080/realms/keycloak-patterns + jwk-set-uri: http://host.docker.internal:8080/realms/keycloak-patterns/protocol/openid-connect/certs + extra_hosts: ["host.docker.internal:host-gateway"] → 401→200 확인 — 등급: planned
  • EC2 시나리오: KC_HOSTNAME=ec2-xx.compute.amazonaws.com 설정 → browser는 public DNS로 접근, backend도 동일 hostname을 issuer-uri로 — 등급: planned
  • (Keycloak 24+) KC_HOSTNAME_BACKCHANNEL_DYNAMIC=true 시연: frontchannel은 public hostname, backchannel은 container DNS 자동 분리 — 등급: planned
  • 네 해결 방안 비교 노트 (장단점 표) — 등급: planned (2026-07-17: 3→4개. §구현 가이드 §2 의 표가 사전 명세, 실측 후 이 표로 확정)
  • 학습 정리: "왜 iss claim 검증이 신뢰의 핵심인가" 설명문 작성 — 등급: planned

진행 중 메모

  • iss 검증이 신뢰의 본질: 만약 backend가 iss 검증을 안 하면 다른 Keycloak realm(또는 가짜 IdP)의 token도 통과. RFC 7519 + OIDC Core spec 모두 iss 검증을 mandatory로 규정.
    • (2026-07-17) "RFC 7519 + OIDC Core 가 mandatory 로 규정" 은 본 branch Sources 로 미증명 — RFC/OIDC 원문이 Sources 에 없다. Spring 측은 SSRS-JWT-C2(discovery 4단계의 4번이 iss 비교)로 동작은 확정되나, 스펙이 mandatory 라고 규정한다는 진술은 별개. §Claims To Verify 참조.

  • Spring issuer-uri는 두 가지 역할:
    1. OIDC discovery (/.well-known/openid-configuration) URL 생성 — JWKS endpoint 자동 찾기
    2. JWT iss claim 검증 시 기대값
    • (2026-07-17 핵심) 이 메모가 D6 의 씨앗이었다. SSRS-JWT-C5 는 이 두 역할이 분리 가능함을 공식으로 확정한다 — jwk-set-uri 를 주면 Spring 은 역할 1(discovery)을 아예 수행하지 않고, issuer-uri 는 역할 2(문자열 검증)만 남는다. 함정의 원인은 "두 역할이 한 값에 묶여 있다" 는 기본 auto-config 의 우연한 결합이지 OIDC 의 요구가 아니다.

  • token 안의 iss는 Keycloak이 박음KC_HOSTNAME이 결정. backend가 어디서 JWKS를 fetch하든 token 안의 iss와 backend 기대값이 일치해야 함.
    • (2026-07-17) "backend 가 어디서 JWKS 를 fetch 하든" — 이 표현이 정확히 F 의 원리다. 작성 시점엔 원리를 적어두고 해결 방안에는 반영하지 않았다(§Audit & Findings A1).

  • 함정 변형: browser는 EC2 public DNS, backend는 docker internal — Keycloak이 어느 hostname으로 발급할지가 KC_HOSTNAME에 의존. 만약 미설정이면 Keycloak이 request Host 헤더 기준으로 추측 → 변동성 발생.
    • (2026-07-17) KC-HOST-C2(hostname 설정 의무 + dynamic resolution 차단)와 정합. 단 미설정 시 startup 이 실패하는지 vs 추측하는지는 hostname guide 의 §Usage Boundaries 가 명시적으로 "본 인용에 없음" 이라고 적은 항목 — §Claims To Verify.

  • KC_HOSTNAME_BACKCHANNEL_DYNAMIC: Keycloak 24+ 신기능. frontchannel URL은 KC_HOSTNAME 고정, backchannel(=internal service-to-service)은 request로부터 동적으로 결정. 단일 host에서 매우 유용.
    • (2026-07-17 정정 2건) ① "24+ 신기능" → 정확히는 hostname v2(25.0.0 도입, KC-2500-C1/C3)의 옵션이며 26.0 에서 v1 이 제거되어(KC-2600-C1) 26.x 에선 v2 가 유일. v1 의 대응 옵션은 hostname-strict-backchannel이름뿐 아니라 boolean 극성이 반대였다. ② "단일 host 에서 매우 유용" → 본 branch 에선 유용하지 않다. Spring 기본 auto-config 는 issuer-uri 문자열로 discovery 를 호출하므로(SSRS-JWT-C2), Keycloak 이 backchannel URL 을 어떻게 응답하든 backend 의 discovery 호출 자체가 localhost(=자기 자신)로 나가 네트워크 단계에서 먼저 실패한다. D 를 쓰려면 결국 F 와 같은 Spring 측 분리 설정이 필요 → D 단독의 부가가치는 "여러 client 종류를 한 곳에서 관리" 에 국한 (D6 참조).

결정 사항 (decisions)

  • 2026-05-25: KC_HOSTNAME=localhost 강제. 이유: 학습 단계 일관성, P3A 본질 함정 시연.
  • 2026-05-25: 세 해결 방안 모두 학습. 이유: 면접에서 "왜 A가 아니라 B를 골랐냐"에 답하려면 비교가 필수.
  • 2026-05-25: EC2 시나리오는 docker-compose 환경에서 시뮬레이션만. 실제 EC2 배포는 별도 마일스톤.
  • 2026-05-25: 실패 재현을 먼저, 해결을 뒤에. 이유: 함정의 원인을 코드/로그로 직접 보지 않으면 학습 효과 낮음.
  • 2026-07-18: 기본 해결 경로 = (F) issuer/JWKS 분리. KC_HOSTNAME=localhostissuer-uri=localhost로 identity를 고정하고, jwk-set-uri=host.docker.internal로 network address를 분리한다. C(localhost:host-gateway)는 /etc/hosts 중복 우선순위 때문에 fallback 비교군으로 강등한다.
  • 2026-07-17: hostname-strict=false 는 해법 후보에서 제외. 이유: ① 보안 — dynamic hostname 해석은 KC-HOST-C3 의 fraudulent issuer 위험을 정면으로 허용, ② 기능 — 애초에 이 함정을 해결하지 못한다 (request 마다 iss 가 달라져 문자열 일치가 아예 불가). 학습 환경이라도 채택 안 함. 근거: raw/official-docs/keycloak-hostname-configuration

결정-근거 매핑

각 결정의 직접 근거. 선택 조건 열(R2)은 "이 조건일 때 이 결정, 다른 조건이면 어떤 대안" — 분기 없으면 N/A. (2026-07-17) 기존 D1~D5 의 Supporting Claims 는 보존하고, 자동조사로 확정된 근거를 반영해 D2 의 Open Risk 를 갱신 + D6·D7 신규 추가.

Decision ID Decision 선택 조건 (언제 이 결정 / 언제 대안) Supporting Claims Evidence Strength Open Risk
D1 KC_HOSTNAME=localhost 강제 (학습 단계 일관성, P3A 본질 함정 시연) 단일 호스트 학습 환경에서 browser 접근점이 localhost 일 때. 대안: browser 가 EC2 public DNS 로 접근하면 KC_HOSTNAME=<public DNS> (D3 의 시뮬레이션 범위). hostname 미설정은 선택지가 아님KC-HOST-C2 가 설정을 의무화 raw/official-docs/keycloak-hostname-configuration.md#KC-HOST-C2 (hostname 설정 의무), raw/official-docs/keycloak-hostname-configuration.md#KC-HOST-C3 (fraudulent issuer 방지) official-vendor-doc hostname-strict=false는 D7에 따라 제외. 최종 wiring owner는 raw/branch-notes/feature-keycloak-single-ec2-no-google D3이며 본 행은 함정 시연 범위다.
D2 세 해결 방안 모두 학습 (A: KC_HOSTNAME + extra_hosts, B: network_mode host, C: extra_hosts 응용) 면접에서 "왜 A 가 아니라 B 냐" 에 답해야 하므로 비교 자체가 목표 — 하나만 실습하는 대안은 학습 목표상 기각. (2026-07-17: 비교 대상이 A/B/C → A/B/C/F 로 확장, D6 가 선택 기준을 부여) raw/official-docs/keycloak-hostname-configuration.md#KC-HOST-C1 (frontchannel/backchannel 분리 capability), raw/official-docs/keycloak-hostname-configuration.md#KC-HOST-C4 (backchannel-dynamic full URL 요구) official-vendor-doc network_mode: host (Linux only) / extra_hosts: host-gateway (Docker 20.10+) 의 동작은 본 Source 가 직접 증명하지 않음 — Docker 공식 doc 별도 필요2026-07-17 해소: DOCKER-HOSTNET-C1C5, DOCKER-COMPOSE-NET-C3C5, DOCKER-2010-C1 아카이브 완료. 판정 2건: "Linux only" = 부분 refute(Desktop 4.34+ opt-in 지원), "20.10+" = 부분 confirm(host.docker.internal 은 20.10.0 확정, host-gateway 리터럴 자체의 도입 버전은 release notes 로 미확정 — moby/moby#40007 원문 필요)
D3 EC2 시나리오는 docker-compose 환경에서 시뮬레이션만, 실제 EC2 배포는 별도 마일스톤 학습 목표가 iss 함정의 이해 이고 EC2 운영이 아닐 때. 대안: 실제 EC2 배포는 프로젝트 §Phase 진행 후 별도 마일스톤 UNSUPPORTED_DECISION (운영 우선순위 결정) UNSUPPORTED_DECISION 실제 EC2 배포 미수행 — 면접/포트폴리오에 EC2 운영 경험을 주장하면 안 된다. Docker dev의 host.docker.internal 선택을 EC2/prod 값으로 일반화하지 않는다.
D4 실패 재현을 먼저, 해결을 뒤에 (함정의 원인을 코드/로그로 직접 보지 않으면 학습 효과 낮음) 학습 목적 branch 일 때. 대안: 납기 압박이 있는 실무 branch 라면 해결부터 (본 branch 는 해당 없음) UNSUPPORTED_DECISION (학습 방법론 결정 — 외부 자료가 뒷받침하지 않는 본 branch 본문의 자체 판단) UNSUPPORTED_DECISION 본 결정은 학습 효율 가설. 결과 측정 (실패 재현 전후 이해도 차이) 자체로만 verified 가능 — 외부 source corroborate 불가
D5 "iss 검증이 신뢰의 본질, iss 검증을 안 하면 다른 realm/가짜 IdP token 통과" 라는 진행 중 메모 통찰 N/A (분기 없는 원리 진술) raw/official-docs/keycloak-hostname-configuration.md#KC-HOST-C3 (fraudulent issuer 방지 rationale), raw/official-docs/spring-security-resource-server-jwt.md#SSRS-JWT-C1, raw/official-docs/spring-security-resource-server-jwt.md#SSRS-JWT-C2 (issuer-uri 와 token iss 정확 일치 검증) — 2026-07-17: Spring RS 를 본 branch Sources 에 정식 링크 완료 (기존 "보조 인용" 단서 해소) official-vendor-doc "RFC 7519 + OIDC Core spec 모두 iss 검증을 mandatory" 라는 본문 진술은 본 branch Source (Keycloak hostname + Spring RS) 가 직접 증명하지 않음 — 두 Source 는 구현 동작만 증명. RFC 7519 §4.1.1 또는 OIDC Core §3.1.3.7 정독으로 corroborate 필요 (미해소)
D6 기본 = (F) issuer identity/JWKS network address 분리issuer-uri=http://localhost:8080/realms/keycloak-patterns, jwk-set-uri=http://host.docker.internal:8080/realms/keycloak-patterns/protocol/openid-connect/certs Docker dev bridge profile에서 token identity는 browser-visible localhost로 유지하고 backend JWKS fetch만 host gateway로 보낸다. Linux에서는 backend extra_hosts: ["host.docker.internal:host-gateway"]가 필요하다. C/B는 fallback 비교군 raw/official-docs/spring-security-resource-server-jwt.md#SSRS-JWT-C5, raw/official-docs/docker-compose-networking-extra-hosts-official.md#DOCKER-COMPOSE-NET-C3, #DOCKER-COMPOSE-NET-C4 official-vendor-doc realm/JWKS path와 key rotation cache는 runtime 미검증 → needs-confirmation. 실제 401→200 및 key rotation E2E 전에는 planned
D7 hostname-strict=false 를 해법 후보에서 제외 N/A — 조건부 아님(단정적 제외). 유일 예외: hostname-debug=true 와 함께 동적 해석 동작 관찰용으로 일시 사용 후 원복 raw/official-docs/keycloak-hostname-configuration.md#KC-HOST-C3 (명시적 hostname 설정이 fraudulent issuer 를 방지), raw/official-docs/keycloak-hostname-configuration.md#KC-HOST-C5 (strict 기본 true, prod 는 항상 true 권장), raw/official-docs/keycloak-hostname-configuration.md#KC-HOST-C2 (기본 동작이 dynamic resolution 을 차단하는 것이 보안 조치) official-vendor-doc 조사가 확보한 "hostname 설정 시 strict 무시 / hostname 미설정 시 backchannel-dynamic 강제 false" Validations 규칙과 password-reset 링크 조작 공격 시나리오는 아직 아카이브 안 됨 (현행 hostname guide 의 신규 claim 후보 KC-HOST-C6). 제외 결정 자체는 위 3개 claim 으로 충분하나, "기능적으로도 해법이 아니다" 의 1차 근거는 미아카이브 → §Claims To Verify

구현 가이드

본 branch 의 결정(D1·D2·D4·D6·D7)에서 도출되는 구현 detail 만. realm/client 생성(realm-client-export 소유), aud 검증(audience-validator 소유), EC2 실배포(D3 로 제외)는 §범위 Out of scope 로 이관 — 본 § 에 남기지 않는다(R3). ⚠️ 전 항목 plannedkeycloak-patterns repo 부재(NO_GROUND_TRUTH). 아래는 사전 명세이며 코드로 확인된 사실이 아니다.

1. 실패 재현 명세 (의도적 mismatch)

Trace: D4(실패 먼저) + D1(KC_HOSTNAMEiss 를 결정) ← KC-HOST-C2. 관찰 대상 메커니즘은 SSRS-JWT-C2(discovery 4단계의 4번 = ississuer-uri 와 비교).

  • UNSUPPORTED_IMPL_DECISION: 아래 "재현 조건" 의 구체 조합(KC_HOSTNAME 제거 + KC_HTTP_ENABLED=true 만)은 공식 문서가 권고하는 구성이 아니라 함정을 만들기 위한 의도적 오구성이다. KC-HOST-C2 는 hostname 설정을 의무화할 뿐 "미설정 시 무엇이 일어나는지" 는 명시하지 않는다(hostname guide §Usage Boundaries 가 스스로 미증명이라고 기록) — 재현 결과는 실측으로만 확정. trade-off: 학습 목적상 공식이 금지한 구성을 일부러 만드는 것이 이 branch 의 가치.
항목 명세 근거
Keycloak 설정 KC_HOSTNAME 미설정, KC_HTTP_ENABLED=true D4 재현 조건 (의도적 오구성)
backend 설정 spring.security.oauth2.resourceserver.jwt.issuer-uri: http://keycloak:8080/realms/keycloak-patterns (Docker 내부 DNS — 함정의 원인) DOCKER-COMPOSE-NET-C2 (서비스명 DNS 는 도달됨 → 네트워크는 성공하고 검증만 실패하는 것이 이 함정의 교육 포인트)
관찰점 1 backend 응답: GET /api/me401 D4
관찰점 2 backend 로그의 예외 클래스명 + 메시지 verbatim 캡처. ⚠️ 실패가 어느 단계에서 나는지가 미확정SSRS-JWT-C2 의 discovery 4단계 중 (a) 1단계(keycloak:8080 로 Provider Configuration 조회 → 이 호출은 DOCKER-COMPOSE-NET-C2성공하고, 돌아온 메타데이터의 issuer 필드가 설정한 issuer-uri 와 달라서 실패) 인지 (b) 4단계(token ississuer-uri 와 비교) 인지 아카이브된 claim 이 규정하지 않음. 단계가 다르면 예외 클래스와 교육 포인트가 통째로 바뀐다 (1단계 실패라면 F 의 정당성은 오히려 강화 — F 는 그 단계를 건너뜀). 예외 클래스명으로 판별할 것 §Claims To Verify 1행 + 신규 "실패 단계 판별" 행
관찰점 3 jwt.io 로 decode 한 access_token 의 iss 실측값 §Claims To Verify 2행
대조 관찰점 3(token 의 iss) ≠ backend issuer-uri 임을 두 문자열 나란히 기록 D5 (원리)

2. 해결 경로 4종 설정 명세 (사전 — 실측 전)

Trace: D6(메커니즘 선택) + D2(4종 모두 학습). 각 행의 근거 claim 은 아래 표 근거 열.

  • UNSUPPORTED_IMPL_DECISION: realm 명 keycloak-patterns는 프로젝트 F3에서 상속한다. JWKS 경로 문자열은 runtime .well-known/openid-configurationjwks_uri로 재확인해야 하므로 현재 needs-confirmation이다. F는 2026-07-18 기본으로 채택했지만 실제 401→200과 key rotation 결과 전까지 planned다.
# Keycloak 측 backend 측 (application.yml) Docker 측 iss 일치? (이론 — 실측 전) 근거
A KC_HOSTNAME=localhost issuer-uri: http://host.docker.internal:8080/realms/keycloak-patterns extra_hosts: ["host.docker.internal:host-gateway"] FAIL — token isslocalhost 기준인데 기대값은 host.docker.internal DOCKER-COMPOSE-NET-C3,C4 (메커니즘), SSRS-JWT-C1 (issuer-uri 는 iss 값이어야 함 → 불일치 확정)
B KC_HOSTNAME=localhost issuer-uri: http://localhost:8080/realms/keycloak-patterns network_mode: host (backend) PASS DOCKER-HOSTNET-C1 (Linux native / Desktop 4.34+ opt-in), DOCKER-HOSTNET-C4 (ports: 무시 — compose 재구성 필요), DOCKER-HOSTNET-C3 (Windows 컨테이너 불가)
C KC_HOSTNAME=localhost issuer-uri: http://localhost:8080/realms/keycloak-patterns (무변경) extra_hosts: ["localhost:host-gateway"] (backend) PASS (조건부 — /etc/hosts 중복 우선순위 실측 필요) DOCKER-COMPOSE-NET-C4 (host-gateway), DOCKER-2010-C1 (20.10+), DOCKER-COMPOSE-NET-C3Does not prove (기존 entry 재매핑 미언급 = 이 행의 리스크)
F (기본) KC_HOSTNAME=localhost issuer-uri: http://localhost:8080/realms/keycloak-patterns + jwk-set-uri: http://host.docker.internal:8080/realms/keycloak-patterns/protocol/openid-connect/certs backend extra_hosts: ["host.docker.internal:host-gateway"] PASS 예상 SSRS-JWT-C5, DOCKER-COMPOSE-NET-C3/C4

왜 F가 함정을 없애는가 (D6): iss 문자열 검증과 JWKS fetch 주소를 분리한다. 단 Docker dev에서 host.docker.internal 도달을 위해 Linux의 extra_hosts는 여전히 필요하지만, token iss를 network alias로 바꾸지는 않는다.

3. 검증 관측점 (해결 후)

Trace: D6(어느 경로든 동일 기준으로 판정) + D4(before/after 대조가 학습 산출물). 프로젝트 §Branch 분해표의 본 branch 목표 조건("KC_HOSTNAME 미설정 → iss mismatch 401 재현 → 설정으로 해결(로그 before/after)")과 정합.

관측점 기대 캡처 형태
GET /api/me 401 → 200 응답 상태 + 본문
backend 로그 iss 관련 예외 소멸 before/after 로그 발췌
token iss vs issuer-uri 문자열 동일 두 값 나란히
(F 한정) discovery 호출 부재 backend 가 localhost:8080/.well-known/...호출하지 않음SSRS-JWT-C5 의 "will not ping" 실측 네트워크 로그 또는 Keycloak access log

엣지·실패·의존

  • 실패·엣지 경로:

    • KC_HOSTNAME 미설정 시 Keycloak startup 동작 — 실패하는지 vs Host 헤더로 추측하는지 미확정. hostname guide §Usage Boundaries 가 "hostname 미설정 시의 정확한 startup 동작은 본 인용에 없음"(KC-HOST-C2Does not prove)이라고 명시. 재현 시나리오 자체가 이 동작에 의존하므로 실패 재현이 의도대로 안 될 수 있음(startup 자체가 죽으면 401 이 아니라 서비스 부재).
    • (C) /etc/hosts 중복 entry — fallback 비교군 — base 이미지의 127.0.0.1 localhostextra_hosts: ["localhost:host-gateway"] 우선순위가 미확정이라 기본에서 제외했다. 비교 실험 시 getent hosts localhost로 확인한다.
      • 실습 순서 권고: getent hosts localhost 확인을 초반에 배치 — 기본 경로의 성립 여부가 나머지 계획을 좌우하므로.
    • (B) ports: 무시 → compose D6 무효화DOCKER-HOSTNET-C4: host network mode 에선 -p/ports:경고만 내고 무시된다. 깨지는 구체 계약은 raw/branch-notes/feature-keycloak-docker-compose-stack D6(keycloak 8080:8080, app 8081:8081, nginx 80:80) — B 를 backend 에 켜면 app 8081:8081 게시가 무효가 되어 브라우저의 backend 직접 접근이 조용히 깨진다.
    • (B) Docker Desktop 게이트DOCKER-HOSTNET-C1/C2/C5: 4.34 미만이면 아예 미지원, 이상이어도 수동 활성화 + layer 4 한정. macOS/Windows 개발자는 재현 불가할 수 있음.
    • (F) JWKS key rotationjwk-set-uri 수동 지정 시 Keycloak 이 서명 키를 rotate 하면 캐시 갱신이 discovery 경로와 동일하게 동작하는지 미확정(SSRS-JWT-C2 가 retry/backoff 를 범위 밖으로 명시) → 장기 실행 시 401 재발 가능.
    • EC2 확장 시 hairpin NAT — Docker dev의 host.docker.internal profile을 EC2에 그대로 적용하지 않는다. EC2/prod의 JWKS network address는 배포 topology owner가 별도로 결정해야 한다(needs-confirmation).
    • 토큰 만료·clock skew — 본 branch 범위 밖(iss 만 다룸). exp/nbf 검증 실패를 iss 함정으로 오진하지 않도록 실패 재현 시 예외 클래스명까지 확인할 것.
  • 다른 계약 의존 (대상 브랜치 + Decision ID 입도):

    • raw/branch-notes/feature-keycloak-single-ec2-no-google D3 — 최종 Docker dev wiring owner. 2026-07-18 sync에서 F(issuer/JWKS 분리)를 기본으로 정렬했다.
    • raw/branch-notes/feature-keycloak-spring-rs-audience-validator D6 — Spring RS wiring owner. issuer-uri + explicit jwk-set-uri profile로 정렬하며 key rotation cache는 needs-confirmation이다.
    • raw/branch-notes/feature-keycloak-spring-rs-audience-validator D5 — 그 D5("JWKS cache 기본 정책 5분, kid mismatch 시 자동 refresh", UNSUPPORTED_DECISION)는 본 branch D6 의 F Open Risk("jwk-set-uri 수동 지정 시 key rotation 캐시 갱신 미확인")와 동일한 미지수다. 두 노트가 같은 공백을 각자 들고 있음 — 해소 시 공동 처리.
    • raw/branch-notes/feature-keycloak-docker-compose-stack D3⚠️ F 가 이 결정의 트리거 조건을 소거한다. 그 D3(depends_on: condition: service_healthy)의 선택 조건은 "app 이 startup 시 keycloak JWKS/issuer discovery 에 의존할 때 이 결정" 인데, SSRS-JWT-C5 는 F 하에서 RS 가 "will not ping the authorization server at startup" 이라 하고 jwk-set-uri 의 사용 동기 자체가 "initialize independently from the authorization server" 다 → F 채택 시 D3 의 근거가 약화(첫 요청 시점 도달성만 필요). D3 owner 에게 전파 필요.
    • raw/branch-notes/feature-keycloak-docker-compose-stack D6 — port 매핑(keycloak 8080:8080, app 8081:8081, nginx 80:80)이 본 branch §구현 가이드 §2 표의 모든 :8080 과 F 의 jwk-set-uri 포트의 출처. 서비스명이 keycloak 이 아니게 되거나 포트가 바뀌면 F 의 jwk-set-uri 와 A/C 의 extra_hosts 대상이 함께 깨진다. 해결 (B) 는 이 D6 을 무효화(위 실패·엣지 참조).
    • raw/branch-notes/feature-keycloak-docker-compose-stack D5 — localhost identity를 구현하는 Compose consumer. 값 변경 권한은 parent D3에 있고 본 branch D1은 실패 시연만 소유한다.
    • raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce D1 — 프로젝트 §Branch 분해표가 본 branch 의 선행 의존으로 지정. 그 D1(oidc-client-ts 우선 채택)이 token 을 실제 발급받는 경로 → token 이 없으면 iss 를 관찰할 수 없다(재현 자체가 불가).
    • raw/branch-notes/feature-keycloak-realm-client-export — realm keycloak-patterns 존재가 iss 문자열(.../realms/keycloak-patterns)의 전제. 프로젝트 F3(단일 공유 realm keycloak-patterns)이 SSOT.

검증해야 할 주장

(2026-07-17) 자동조사로 판정된 3건은 Status 를 resolved-by-source 로 갱신하고 판정 내용을 §Audit & Findings 에 기록. 나머지는 실측으로만 닫힌다.

Claim Why uncertain How to verify Status
KC_HOSTNAME=localhost 미설정 + KC_HTTP_ENABLED=true 만으로 backend 가 정확히 JwtValidationException / iss claim did not match 메시지를 로그에 남기는지 Spring Security 6.x 의 정확한 에러 메시지 텍스트는 버전마다 다를 수 있음 docker-compose 로 환경 띄우고 backend 로그 캡처, 메시지 verbatim 기록 planned
Decoded access_token 의 iss claim 이 정확히 http://localhost:8080/realms/keycloak-patterns 형태로 박히는지 Keycloak 26.x 의 iss 생성 규칙은 KC_HOSTNAME + realm path 결합이라 가정 — verbatim Source 부재 (hostname guide §Usage Boundaries 가 "정확한 string concatenation 은 본 페이지에 명시 없음" 으로 스스로 기록) jwt.io 로 token decode 후 iss 값 캡처 needs-confirmation
해결 (A) KC_HOSTNAME=localhost + backend extra_hosts: ["host.docker.internal:host-gateway"] 조합이 실제 e2e 로 401 → 200 으로 전환되는지 본 branch 본문 자체가 "issuer-uri 와 token iss mismatch" 함정을 재인지함 — 정답은 "token issuer 와 backend issuer-uri 를 정확히 일치". 2026-07-17: SSRS-JWT-C1 기준 A 원안은 이론적으로 FAIL 로 판정 — 실측은 "실패함" 을 확인하는 대조군 docker-compose 환경 구성 후 GET /api/me 200 응답 확인 (200 이 나오면 오히려 이론 판정이 틀린 것 → 재조사) planned
network_mode: host 가 macOS/Windows Docker Desktop 에서 동작 안 함 + Linux only 진술의 정확한 vendor 출처 본 branch 본문 메모 — 직접 source 인용 부재 Docker 공식 doc (network_mode 페이지) 또는 Docker Desktop release note 정독 resolved-by-source (2026-07-17)부분 refute. raw/official-docs/docker-host-network-driver-official DOCKER-HOSTNET-C1/C2: Linux native + Docker Desktop 4.34+ 에서 opt-in 지원(Settings 수동 활성화). "Linux only" 는 무조건 진술로는 부정확. 단 C5(layer 4 한정) + C3(Windows 컨테이너 불가)로 제약은 실재
extra_hosts: host-gateway 의 최소 Docker 버전 (20.10+) 의 정확한 source 본 branch 본문 메모 — verbatim source 부재 Docker Compose 공식 spec 또는 docker engine release note 확인 resolved-by-source (2026-07-17)부분 confirm. raw/official-docs/docker-engine-20-10-release-notes-official DOCKER-2010-C1: host.docker.internal 의 Linux dockerd 지원은 20.10.0(2020-12-08) 확정. 단 host-gateway 리터럴 자체의 도입 버전은 이 release notes 페이지로 미확정(문자열이 20.10.23 버그수정 항목에만 등장) → 완전 확정하려면 moby/moby#40007 원문 필요
Keycloak 26.x 에서 KC_HOSTNAME_BACKCHANNEL_DYNAMIC 옵션이 실제 frontchannel/backchannel URL 을 자동 분리하는지 KC-HOST-C1/C4 가 capability 자체는 증명하나, 본 프로젝트 단일 host 시나리오에서의 실 동작은 별도. 2026-07-17 조사도 공식 근거를 못 찾음 — hostname guide(v2) 본문에 issuer 라는 단어 자체가 없음. v1 문서(24.0.5)에는 "the issuer is also based on the URL set to the frontend endpoints" 가 있었으나 v2 가 재확인하지 않음 docker-compose 환경에 옵션 추가 후 두 경로에서 각각 .well-known/openid-configuration 호출해 issuer 값 비교. KC_HOSTNAME_DEBUG=true + /realms/master/hostname-debug 병행 권고 planned
Keycloak 24+ 에서 26.x 까지 KC_HOSTNAME_* 옵션 명칭이 자주 바뀐다는 본문 진술 본 branch 본문 메모 — Source 부재 Keycloak release notes (24, 25, 26) 정독, 옵션 rename 이력 정리 resolved-by-source (2026-07-17)부분 confirm / 프레이밍 refute. KC-2500-C1~C4 + KC-2600-C1: 개편은 실재하나 "26.x 안에서 자주" 가 아니라 25.0.0 에서 v2 도입(v1 deprecated) → 26.0.0 에서 v1 제거1회 대개편이며 26.x point release 간에는 안정적. 정확한 진술: "한 번 크게 바뀌었고 그 시점은 26.0 이전에 종료"
iss 검증을 안 하면 다른 Keycloak realm 또는 가짜 IdP token 통과하는지 의 실 재현 본 branch 본문 메모. KC-HOST-C3 는 일반 rationale 만 — 가짜 IdP 시나리오 직접 증명 안 함 두 번째 Keycloak realm 또는 미니멀 fake JWT issuer 띄우고 token 발급 → backend 가 거부하는지 확인 (현재 hostname-strict + audience validator 와 결합) planned
(신규 2026-07-17) 해결 (C) 에서 extra_hosts: ["localhost:host-gateway"] 가 base 이미지의 기존 127.0.0.1 localhost entry 를 실제로 이기는지 Docker 공식이 이 케이스(기존 hostname 재매핑)를 명시 안 함 — DOCKER-COMPOSE-NET-C3Does not prove 가 경계를 기록 컨테이너 내부 getent hosts localhost 로 실제 resolve IP 확인 needs-confirmation
(신규 2026-07-17) 해결 (F) 에서 jwk-set-uri 수동 지정 시 Keycloak key rotation 과의 캐시 갱신 상호작용 SSRS-JWT-C2 가 "discovery 실패 시 retry/backoff 정책은 범위 밖" 으로 명시 — rotation 시 JWKS 재fetch 정책 불명 raw/official-docs/jwks-keycloak-key-rotation-active-passive 정독 + F 조합에서 키 rotate 후 401 재발 여부 실측 needs-confirmation
(신규 2026-07-17) JWKS endpoint 경로 /realms/<realm>/protocol/openid-connect/certs 가 26.x 의 실제 값인지 본 branch Sources 중 어느 것도 이 경로 문자열을 증명하지 않음 (구현 가이드 §2 의 UNSUPPORTED_IMPL_DECISION) .well-known/openid-configurationjwks_uri 필드 실측값으로 확정 needs-confirmation
(신규 2026-07-17) "hostname 설정 시 hostname-strict 무시 / hostname 미설정 시 backchannel-dynamic 강제 false" Validations 규칙 + password-reset 링크 조작 공격 시나리오 조사가 hostname guide(v2) 원문에서 확보했으나 아직 아카이브 안 됨 — D7 의 "기능적으로도 해법이 아니다" 논거의 1차 근거 현행 raw/official-docs/keycloak-hostname-configurationKC-HOST-C6 로 추가 아카이브 (기존 파일 갱신 — 신규 파일 아님) needs-confirmation
(신규 2026-07-17, depth 감사 F4) 실패 재현 시 401 이 discovery 1단계(메타데이터 issuer 필드 대조)에서 나는지 token iss 검증 4단계에서 나는지 SSRS-JWT-C2 는 4단계를 나열할 뿐 어느 단계가 mismatch 를 먼저 잡는지 규정 안 함(그 claim 의 Does not prove 는 retry/backoff 만 배제). §구현 가이드 §1 재현 구성은 issuer-uri: http://keycloak:8080/... 이라 discovery 호출 자체는 성공한다 → 실패 지점이 두 후보로 갈림 backend 로그의 예외 클래스명으로 판별 (discovery 단계 실패면 JwtDecoderInitializationException 계열, iss 검증 실패면 JwtValidationException 계열로 추정 — 실측으로 확정). 필요 시 Spring §Startup Expectations 잔여 문단을 기존 raw 에 추가 아카이브 needs-confirmation
(신규 2026-07-17, depth 감사 F7) SSRS-JWT-C5 의 "will not ping … at startup" 이 first-request 시점 discovery 까지 배제하는지 SSRS-JWT-C2 는 discovery 가 "at the first request containing a JWT" 에 시작된다고 함 → "startup 에 안 한다" 가 "영원히 안 한다" 를 verbatim 으로 닫지는 않음. §제목("… JWK Set Uri Directly") + "Consequently" 인과 구조상 discovery 자체를 건너뛴다는 독해가 자연스러우나 명시 아님 §구현 가이드 §3 의 "(F 한정) discovery 호출 부재" 관측점으로 실측. 부수적으로 spring-security-resource-server-jwt.mdSSRS-JWT-C5 Does not prove 열에 이 경계 1줄 추가 권고 needs-confirmation

Audit & Findings (2026-07-17 /branch-spec 자동조사)

본 § 는 조사 결과 중 결정으로 흡수되지 않은 발견·정합 권고만 보존 (CLAUDE.md §15.5 R3 — 이관 history 는 별도 § 에).

ID 유형 발견 조치
A1 MISSING_EVIDENCE_LINK (해소됨) raw/official-docs/spring-security-resource-server-jwt이미 이 repo 에 아카이브돼 있었고 SSRS-JWT-C5 가 D6 의 정답을 담고 있었으나, 본 branch 의 Sources 표에 링크되지 않아 해결 방안이 Docker 레이어(A/B/C)로만 좁혀져 있었다. 노트의 §진행 중 메모("backend 가 어디서 JWKS 를 fetch 하든")는 이미 원리를 알고 있었으나 해결 방안에 반영되지 않음 2026-07-17 Sources 표에 정식 링크 + D6 신설 + In scope 에 F 추가 (해소)
A2 CROSS_BRANCH_DECISION_TENSION parent D3과 본 D6가 Docker layer vs issuer/JWKS split을 달리 가리켰음 해소 (2026-07-18) — parent D3을 owner로 유지하고 F profile을 기본으로 정렬. 본 D1은 실패 시연 범위만 소유
A7 CROSS_BRANCH_DECISION_CONFLICT audience-validator D6의 discovery-only wiring과 본 D6의 explicit jwk-set-uri가 충돌했음 해소 (2026-07-18) — Spring RS owner도 explicit jwk-set-uri Docker dev profile을 허용하도록 정렬. key rotation cache는 runtime needs-confirmation으로 남김
A8 TRIGGER_CONDITION_ERODED (미해소 — depth 감사 F3 발견) raw/branch-notes/feature-keycloak-docker-compose-stack D3(depends_on: condition: service_healthy)의 선택 조건은 "app 이 startup 시 keycloak JWKS/issuer discovery 에 의존할 때" 인데, SSRS-JWT-C5("will not ping … at startup" + "initialize independently from the authorization server")에 따라 F 는 그 트리거 조건을 약화시킨다(첫 요청 시점 도달성만 필요) → 완전 소거 여부는 F7 확정에 종속SSRS-JWT-C5 의 "at startup" 이 first-request 시점 discovery 까지 배제하는지가 needs-confirmation 이므로, 현재 정확한 강도는 "약화"다 본 branch 결정 아님(owner = docker-compose-stack). §엣지·실패·의존에 명시 완료. F 승인 시 D3 owner 에게 전파 필요 — healthcheck 를 유지할지(다른 이유: PostgreSQL 의존 등)는 그 branch 판단
A3 DOC_DRIFT (부모 노트, 미해소) 부모 노트 D5 는 "realm 1개 + client 1개(spa-client, public)" 라고 적었으나, 프로젝트 노트 F3(고정 결정, SSOT)은 "단일 공유 realm keycloak-patterns, 패턴당 client 1개 — spa-public / token-mediating-confidential / bff-confidential / edge-proxy" 로 client 명이 다름 본 branch 범위 밖(부모 노트 소유). 본 branch 는 realm 명 keycloak-patterns(F3 정합)만 사용하고 client 명은 참조 안 함. /sync 대상으로 보고
A4 ALTERNATIVE_REJECTED 조사가 5번째 대안 E — network_mode: "service:<anchor>" (더미 anchor 컨테이너의 network namespace 를 Keycloak+backend 가 공유 → 컨테이너 안에서 localhost:8080 이 문자 그대로 동작, 플랫폼/버전 게이트 없음)를 발견. iss 판정 PASS 채택 안 함 — anchor 컨테이너가 죽으면 두 서비스 네트워크 전체가 죽고, 공유 서비스의 모든 포트를 anchor 의 ports: 에 나열해야 함. 서비스 2개 규모에 과설계. §범위 Out of scope 에 기록. 서비스 3개+ 가 동일 localhost identity 를 요구하면 재검토
A5 DROPPED_CANDIDATE 조사가 "컨테이너 IP 를 docker inspect 로 확인해 browser/backend 모두 그 IP 로 접속" 안을 탈락시킴 — docker compose up 재기동마다 IP 가 바뀌어 issuer-uri/KC_HOSTNAME 고정 불가(재현성 없음) 기록만. 실습 불필요
A6 UNARCHIVED_EVIDENCE 조사가 확보한 hostname guide(v2) Validations 규칙("hostname 설정 시 hostname-strict 무시" / "hostname 미설정 시 backchannel-dynamic 강제 false")과 password-reset 링크 조작 공격 시나리오 verbatim 은 D7 의 핵심 논거이나 아직 아카이브 안 됨. 기존 raw/official-docs/keycloak-hostname-configuration 의 신규 claim(KC-HOST-C6) 후보 — 신규 파일이 아니라 기존 파일 갱신이라 wiki-source-summarizer 계약 밖 §Claims To Verify 에 등록. 다음 세션에 수기 또는 wiki-doc-author mode=migrate 로 기존 파일에 추가 권고

마주친 문제

⚠️ (2026-07-17) 아래 3건은 "(구현 시작 후 추가)" 로 적혀 있으나 구현은 시작된 적이 없다(repo 부재 — NO_GROUND_TRUTH). 실제로는 예상 문제 메모이며, 2026-07-17 자동조사가 공식 문서로 판정했다. 실측 근거가 아니므로 면접에서 "겪었다" 로 말하면 안 된다.

  • (구현 시작 후 추가) network_mode: host 사용 시 macOS/Windows Docker Desktop에서 동작 안 함 — Linux only.
    • 부분 refute (DOCKER-HOSTNET-C1/C2): Docker Desktop 4.34+ 에서 opt-in 지원. "동작 안 함" 은 4.34 미만 또는 미활성 시에 한정. 단 layer 4 한정(C5).
  • (구현 시작 후 추가) extra_hosts: host-gateway 동작이 Docker 버전에 따라 다름 — 최소 Docker 20.10+.
    • 부분 confirm (DOCKER-2010-C1): host.docker.internal 의 Linux dockerd 지원 = 20.10.0. host-gateway 리터럴 자체의 도입 버전은 미확정.
  • (구현 시작 후 추가) Keycloak 26.x에서 KC_HOSTNAME_* 옵션 명칭이 자주 바뀜 — 공식 문서 버전 확인 필요.
    • 프레이밍 refute (KC-2500-C1~C4, KC-2600-C1): "자주" 가 아니라 25.0.0 v2 도입 → 26.0.0 v1 제거의 1회 대개편. 26.x 안에서는 안정적.

묶음

본 sub-sub-branch 는 leaf — 자식 자료 없음. Phase 3 P3A 실 구현 또는 외부 산출물 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화. (2026-07-17) 근거 자료는 §Sources 표가 SSOT — 본 § 에 중복 나열하지 않는다(병행 dispatch 가 만든 ### Sources 서브섹션 제거).

오류 기록 (이 sub-sub-branch 작업 중 발생)

  • (없음 — 현재 documented-only 단계)

면접 준비 (이 작업에서 나올 수 있는 면접 질문)

  • (없음 — Phase 3 실 구현 단계에 누적)

관련 일일 노트

완료 후 정리

실패 재현 → 해결 검증 전체를 로그/screenshot으로 캡처 시 plannedactually-implemented/locally-verified 승급. 본 sub-sub는 학습 가치가 핵심 — 실제로 함정을 "맞아본" 경험이 면접 자산.

  • PR 링크: (별도 keycloak-patterns repo)
  • 리뷰 메모:
  • 머지 결과 / 배포 환경: 로컬 docker-compose / 단일 EC2 시뮬레이션
  • wiki 추출 대상:
    • actually-implemented 항목: (구현 후 채움)
    • locally-verified 항목: (구현 후 채움)
    • prod-verified 항목: (없음)
  • 추출하지 않을 항목: 현재 전부 planned.