15 KiB
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 |
|
|
1 | 86daea4a925a49815e0f059211614d4a3646864432a3b8ab8b00a75243e7eb19 | feature-keycloak-token-mediating-confidential-client |
|
|
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_secretenv 주입 — 등급: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/confidential0건) → 본 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_secretenv 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_codegrant(세션 없이 토큰만 획득해 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-memoryOAuth2AuthorizedClientServicevs 영속(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_secret 을 SPRING_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 설정 정합 의존.
- refresh token 자체가 만료/폐기되어 자동 refresh 실패 → 재인증 유도. 근거 quote 밖("Handling Failure"/
- 다른 계약 의존:
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-preexit=3. governing_docs 부재 +related_projects에ca-*프로젝트 없음(keycloak-patterns-overview) → 완전성 게이트 비적용. 완전성은 프로젝트 hub 의 WI-008 분해로 이미 고정. - depth 판정: Ready (2026-07-25) —
wiki_structure_lint.py --filePASS +branch-depth-auditorBlocking 0 / Should-fix 3 / Advisory 2. Should-fix #2·#3·#5 + Advisory #4 는 본 세션에서 반영. 미해결 Should-fix #1: confidential client 의 token-endpointclient_secret인증 메커니즘 L1 근거(RFC 6749 §4.1.3 / Spring "OAuth2 Client Authentication") 후속 archive 필요 — 착수 전 닫기 권장(Decision Evidence Map D1 Open Risk 참조).
마주친 문제
아직 없음.
묶음 (이 branch에서 파생된 자료)
관련 일일 노트
해당 없음.
완료 후 정리
- PR 링크:
- 리뷰 메모: