Files
llm-wiki/raw/branch-notes/feature-keycloak-token-mediating-confidential-client.md
T

15 KiB

title, source_type, status, id, kind, project, work_item, inherits, refines, overrides, depends_on, imports, delegates, accepts_delegations, contract_packet, contract_packet_sha256, branch, parent_branch, related_projects, tags, created, target_merge, status_label
title source_type status id kind project work_item inherits refines overrides depends_on imports delegates accepts_delegations contract_packet contract_packet_sha256 branch parent_branch related_projects tags created target_merge status_label
branch / feature-keycloak-token-mediating-confidential-client branch-note raw BR-KEYCLOAK-PATTERNS-OVERVIEW-008 project-work-item keycloak-patterns-overview WI-KEYCLOAK-PATTERNS-OVERVIEW-008
DEC-KEYCLOAK-PATTERNS-OVERVIEW-SECRET-BOUNDARY-001@1
DEC-KEYCLOAK-PATTERNS-OVERVIEW-TOKEN-MEDIATING-001@1
WI-KEYCLOAK-PATTERNS-OVERVIEW-002
1 86daea4a925a49815e0f059211614d4a3646864432a3b8ab8b00a75243e7eb19 feature-keycloak-token-mediating-confidential-client
keycloak-patterns-overview
branch
2026-07-24 in-progress

branch: feature-keycloak-token-mediating-confidential-client

부모 (필수)

브랜치 계약 패킷

  • 생성 시 프로젝트 개정: 1
  • 패킷 스키마: contract_packet: 1
  • 완료 조건: confidential backend의 code-token 교환과 server-side refresh 보관이 검증된다

상속한 프로젝트 결정

Decision Ref Project Summary Branch Application Source
DEC-KEYCLOAK-PATTERNS-OVERVIEW-SECRET-BOUNDARY-001@1 confidential client secret은 env var로 주입하고 commit·realm export 평문을 금지한다 WI-KEYCLOAK-PATTERNS-OVERVIEW-008 완료 조건에 적용 [[raw/project-notes/keycloak-patterns-overview]]
DEC-KEYCLOAK-PATTERNS-OVERVIEW-TOKEN-MEDIATING-001@1 AP2 backend가 token을 획득하고 access token만 browser에 전달한다 WI-KEYCLOAK-PATTERNS-OVERVIEW-008 완료 조건에 적용 [[raw/project-notes/keycloak-patterns-overview]]

브랜치 지역 결정

Decision ID Decision Relation Supporting Claims Status

선언한 예외

Override ID Overrides Reason Approval Status

가져온 artifact 계약

Artifact Ref Owner Producer Schema Ref

가져온 프로젝트 계약

Ref Owner 요약 Branch 적용

수신한 위임

Delegation Ref From Concern Status

가져온 흐름 단계

Stage Ref Order Owner Input Action Output

목표

  • WI-KEYCLOAK-PATTERNS-OVERVIEW-008의 완료 조건을 구현한다: confidential backend의 code-token 교환과 server-side refresh 보관이 검증된다

범위

포함 범위

  • 백엔드(confidential client)가 authorization code → token 교환을 서버측에서 수행(client_secret 사용).
  • 획득한 refresh token 을 서버측에 보관(browser 미노출)하고 access 만료 시 자동 refresh 경로 확보.
  • 위 두 가지를 로컬 스택에서 재현·검증(code→token 200, refresh 서버측 보관).

제외 범위

의도적으로 제외. 면접 등에서 "이건 범위에 없었습니다" 근거.

  • access token 을 browser 로 handoff + refresh 가 네트워크탭/응답바디에 부재 확인WI-009(feature-keycloak-token-mediating-access-handoff) 소유. 본 branch 는 획득·보관까지.
  • BFF proxy(모든 API 를 backend 가 대리 호출) 및 CSRF/SameSite 방어 → AP3(feature-keycloak-bff-*).
  • Resource Server 측 aud/iss 검증 → AP1(feature-keycloak-spring-rs-audience-validator).
  • project decision registry 변경.

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

Source 정당화하는 결정
[[raw/official-docs/spring-security-oauth2-login-servlet-official]] D1 — oauth2Login() 이 backend(Spring Boot) 소유의 서버측 OAuth2 client 기능이며 code 교환 콜백({baseUrl}/login/oauth2/code/{registrationId})이 backend route 임 (SPRING-OAUTH2LOGIN-C1·C2·C4)
[[raw/official-docs/spring-security-oauth2-authorized-client-servlet-official]] D2 — 획득 토큰이 OAuth2AuthorizedClient서버측 저장(principal-scoped)되고 만료 시 자동 refresh (SPRING-AUTHZCLIENT-C2·C5, SPRING-OAUTH2LOGIN-C5)
[[raw/project-notes/keycloak-patterns-overview]] D1·D2·D3 — AP2 정의(backend=confidential client, 토큰 backend 획득) + 상속 결정 DEC-…-TOKEN-MEDIATING-001@1·DEC-…-SECRET-BOUNDARY-001@1

⚠️ 근거 범위 한계: 위 두 공식 자료는 AP3/BFF(full-session) 맥락에서 수집됐다. D1·D2 의 메커니즘(backend confidential client 의 code→token 교환·서버측 토큰 보관)은 직접 뒷받침하나, AP2 특유의 "access 만 browser 로, refresh 는 서버 보관" 분기는 이 quote 들이 직접 서술하지 않는다(그 명제·검증은 WI-009). 아래 Open Risk 참조.

TODO

  • Keycloak realm 에 confidential client 등록(WI-002 산출물) + client_secret env 주입 — 등급: planned
  • backend 가 authorization code → token 교환 성공(200, client_secret 사용) — 등급: planned
  • refresh token 서버측 보관 확인(backend 재조회/자동 refresh 가능, browser 미보유) — 등급: planned

진행 중 메모

  • 백킹 코드 repo: hub 는 /home/donghyeon/workspace/keycloak-patterns/ 로 표기하나 해당 경로 부재. 실제 후보 = keycloak-pattern(README 만) / Project-Auth-Server(Spring Boot). AP2 구현 코드 아직 없음(grep: AuthorizedClient/oauth2Login/confidential 0건) → 본 branch 결정은 전부 planned, NO_GROUND_TRUTH(구현 SSOT 부재). repo 경로 정합은 착수 전 확정 필요.

결정 사항

  • 2026-07-24: D1 backend 를 confidential OAuth2 client 로 두고 code→token 교환을 서버측 수행 / 이유: AP2 정의(토큰을 backend 가 획득) + client_secret 은 browser 에 둘 수 없음 / 대안: AP1(public client + PKCE, browser 가 교환·토큰 보유) / 근거: [[raw/official-docs/spring-security-oauth2-login-servlet-official]], [[raw/project-notes/keycloak-patterns-overview]]
  • 2026-07-24: D2 refresh token 을 OAuth2AuthorizedClient 로 서버측 보관(browser 미노출), access 만료 시 자동 refresh / 이유: refresh 노출 시 XSS 1건=장기 세션 탈취, AP2 는 refresh 를 backend 만 보유 / 대안: refresh 도 browser 저장(AP1 스타일, 보안강도 하락) / 근거: [[raw/official-docs/spring-security-oauth2-authorized-client-servlet-official]]
  • 2026-07-24: D3(상속 DEC-…-SECRET-BOUNDARY-001@1) client_secret env var 주입, commit·realm export 평문 금지 / 본 branch 적용점: D1 교환에 쓰는 secret 의 출처

Decision Evidence Map / 결정-근거 매핑

official-vendor-doc(Spring) · project-decision(inherited) 구분. 두 Spring 자료는 AP3/BFF 맥락 수집이라 메커니즘 근거로만 쓰고 AP2 분기는 Open Risk 로 명시.

Decision ID Decision 선택 조건 (언제 이 결정 / 언제 대안) Supporting Claims Evidence Strength Open Risk
D1 backend 를 confidential OAuth2 client 로 두고 authorization code→token 교환을 서버측 수행(client_secret) AP2(backend 가 토큰 획득) → 이 결정 / AP1(public client + PKCE, browser 가 교환) → 대안 raw/official-docs/spring-security-oauth2-login-servlet-official.md#SPRING-OAUTH2LOGIN-C1, #SPRING-OAUTH2LOGIN-C2, #SPRING-OAUTH2LOGIN-C4 official-vendor-doc oauth2Login() 은 full-session 수립(AP3 근접) — AP2 의 "access 만 browser 로" 분기는 이 quote 밖(WI-009). 핵심 gap: confidential client 가 token endpoint 에서 client_secret 으로 인증하는 메커니즘 자체를 어느 cited claim 도 증명 안 함(C1/C2/C4 는 route·소유만 닫음) → RFC 6749 §4.1.3 또는 Spring Security "OAuth2 Client Authentication"(client-authentication-method) 챕터 후속 archive 필요(spring-security-oauth2-login-servlet-official.md:84 이 이미 UNSUPPORTED_DECISION 으로 표기). WI-008 완료 조건의 정중앙 메커니즘이라 착수 전 닫기 권장
D2 획득 refresh token 을 OAuth2AuthorizedClient 로 서버측 보관(principal-scoped), access 만료 시 자동 refresh 서버측 보관(AP2/AP3) → 이 결정 / browser 보관(AP1) → 대안 raw/official-docs/spring-security-oauth2-authorized-client-servlet-official.md#SPRING-AUTHZCLIENT-C2, #SPRING-AUTHZCLIENT-C5, raw/official-docs/spring-security-oauth2-login-servlet-official.md#SPRING-OAUTH2LOGIN-C5 official-vendor-doc 저장 API 존재는 증명하나 "browser 가 refresh 를 절대 미보유"는 이 quote 가 직접 증명 X(WI-009 검증). 기본 in-memory store 프로덕션 적합성 미보증
D3 client_secret 을 env var 로 주입, commit·realm export 평문 금지 항상(confidential client) / N/A raw/project-notes/keycloak-patterns-overview.md (DEC-KEYCLOAK-PATTERNS-OVERVIEW-SECRET-BOUNDARY-001@1, 상속) project-decision(inherited) secret 저장·주입 매체(env vs vault/SOPS) 구체는 배포 결정(본 branch 범위 밖)

구현 가이드

Trace: D1(SPRING-OAUTH2LOGIN-C1/C2/C4) + D2(SPRING-AUTHZCLIENT-C2/C5, SPRING-OAUTH2LOGIN-C5) + D3(inherited). AP2 구현 코드 부재(NO_GROUND_TRUTH)라 아래는 착수 전 사전 명세이며 등급 전부 planned.

  • UNSUPPORTED_IMPL_DECISION: (a) backend 획득 메커니즘oauth2Login()(full-session, 자동 authorized-client 저장) vs 수동 authorization_code grant(세션 없이 토큰만 획득해 access 를 browser 로 넘김). 소스는 oauth2Login()(=session 수립, C3)만 서술 → AP2 의 "access handoff" 요구와는 결이 다름. trade-off: oauth2Login 은 배선 최소지만 session 부작용, 수동 grant 는 AP2 에 정합하나 refresh 보관·자동갱신을 직접 배선해야 함. 방향(유보이나 편향 있음): C3(full-session)이 AP2 의 "access 만 browser 로"(WI-009) 요구와 상충하므로 수동 grant 로 lean — 단 소스가 직접 규정하진 않으니 최종 확정은 WI-009 handoff 메커니즘 결정과 함께 착수 시. (b) refresh 저장소 — 기본 in-memory OAuth2AuthorizedClientService vs 영속(JDBC) 구현. 소스는 API 존재만 증명, 선택 미권고. trade-off: in-memory = 재시작 시 authorized-client 소실→재로그인·배선 최소 / JDBC(JdbcOAuth2AuthorizedClientService) = 영속·재시작 생존이나 스키마·배선↑. 학습 스택이면 in-memory 로 시작.
항목 사전 명세 (planned) Trace
Client 등록 Keycloak realm 에 Access Type: confidential client + client_secret 발급(WI-002 산출물) D3 · depends WI-002
Secret 주입 client_secretSPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_*_CLIENT_SECRET env 로 주입(하드코딩·평문 export 금지) D3(SECRET-BOUNDARY)
Code 교환 backend callback route {{baseUrl}}/login/oauth2/code/{{registrationId}} 에서 code→token 교환(client_secret 사용) D1(SPRING-OAUTH2LOGIN-C4) · UNSUPPORTED_IMPL_DECISION(a)
토큰 보관 획득 토큰을 OAuth2AuthorizedClient(principal-scoped)로 서버측 보관, 만료 시 OAuth2AuthorizedClientManager 자동 refresh — 전제(C5 조건부): registration 에 refresh_token grant / offline_access scope + refresh 수행 가능한 OAuth2AuthorizedClientProvider 구성이 있어야 자동 refresh 동작 D2(SPRING-AUTHZCLIENT-C2/C5, SPRING-OAUTH2LOGIN-C5) · UNSUPPORTED_IMPL_DECISION(b)

엣지·실패·의존

  • 실패·엣지 경로:
    • refresh token 자체가 만료/폐기되어 자동 refresh 실패 → 재인증 유도. 근거 quote 밖("Handling Failure"/OAuth2AuthorizationFailureHandler — 소스 미발췌) → 착수 시 별도 확인.
    • code 교환 실패(invalid_client: secret 불일치 / redirect_uri mismatch) → 401/400. WI-002 client 설정 정합 의존.
  • 다른 계약 의존: WI-KEYCLOAK-PATTERNS-OVERVIEW-002(feature-keycloak-realm-client-export, pin 결정 DEC-KEYCLOAK-PATTERNS-OVERVIEW-REALM-CLIENT-001@1 + DEC-KEYCLOAK-PATTERNS-OVERVIEW-SECRET-BOUNDARY-001@1) 의 confidential client 등록 + client_secret 발급을 consume. 그 계약(client id/secret/redirect_uri)이 바뀌면 D1·D3 영향.

검증해야 할 주장 / Claims To Verify

공식 문서는 메커니즘 근거지만 본 프로젝트 실제 동작을 자동 보장하지 않는다. AP2 코드 미구현이라 전부 구현 후 검증 대상.

Claim Why uncertain How to verify Status
backend 가 confidential client 로 code→token 교환에 성공(200, client_secret 사용) AP2 코드 미구현. oauth2Login vs 수동 grant 미결(UNSUPPORTED_IMPL_DECISION a) 로컬 스택에서 로그인 → backend callback 200 + token endpoint 요청에 client_secret 포함(네트워크/로그) 확인 planned
획득 refresh token 이 서버측에 보관되고 backend 가 재조회/자동 refresh 가능 저장소 구현(in-memory vs JDBC) 미결(UNSUPPORTED_IMPL_DECISION b) access 만료 유도 후 자동 refresh 동작 + OAuth2AuthorizedClientService 로 서버측 조회 확인 planned
client_secret 이 env var 에서 로드되고 commit/realm export 에 평문 부재 주입 배선 미구현 grep 로 하드코딩 부재 + realm export JSON 에 secret 평문 부재 확인 planned

관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)

  • coverage 판정: EXEMPT (2026-07-25) — wiki_structure_lint.py --coverage-pre exit=3. governing_docs 부재 + related_projectsca-* 프로젝트 없음(keycloak-patterns-overview) → 완전성 게이트 비적용. 완전성은 프로젝트 hub 의 WI-008 분해로 이미 고정.
  • depth 판정: Ready (2026-07-25) — wiki_structure_lint.py --file PASS + branch-depth-auditor Blocking 0 / Should-fix 3 / Advisory 2. Should-fix #2·#3·#5 + Advisory #4 는 본 세션에서 반영. 미해결 Should-fix #1: confidential client 의 token-endpoint client_secret 인증 메커니즘 L1 근거(RFC 6749 §4.1.3 / Spring "OAuth2 Client Authentication") 후속 archive 필요 — 착수 전 닫기 권장(Decision Evidence Map D1 Open Risk 참조).

마주친 문제

아직 없음.

묶음 (이 branch에서 파생된 자료)

관련 일일 노트

해당 없음.

완료 후 정리

  • PR 링크:
  • 리뷰 메모: