Files
llm-wiki/raw/branch-notes/feature-keycloak-refresh-rotation-and-logout.md
T

30 KiB
Raw Blame History

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-refresh-rotation-and-logout (refresh token rotation + 로그아웃 흐름 + JWT stateless 한계) branch-note raw BR-KEYCLOAK-PATTERNS-OVERVIEW-007 project-work-item keycloak-patterns-overview WI-KEYCLOAK-PATTERNS-OVERVIEW-007
DEC-KEYCLOAK-PATTERNS-OVERVIEW-SPA-CLIENT-001@1
DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1
WI-KEYCLOAK-PATTERNS-OVERVIEW-004
1 feature-keycloak-refresh-rotation-and-logout
keycloak-patterns
branch
keycloak-patterns
p3a
implementation
refresh-token
rotation
logout
jwt-revocation
2026-05-25 in-progress 64fd889a4fded12eee171744601a4a80a43037d485ca72fbf8491e80bc3067bd

branch: feature-keycloak-refresh-rotation-and-logout (refresh token rotation + 로그아웃 흐름)

Layer: raw/branch-notes/raw/project-notes/keycloak-patterns-overview Work Item의 직접 자식 branch. P3A는 실 구현 대상. 본 sub-sub는 학습 + 작업 plan 기록 — 실 구현은 /home/donghyeon/workspace/keycloak-patterns/.

부모 (필수)

raw/project-notes/keycloak-patterns-overview

브랜치 계약 패킷

  • 생성 시 프로젝트 개정: 1
  • 패킷 스키마: contract_packet: 1
  • 완료 조건: refresh rotation과 logout 후 session·token 무효화가 검증된다

상속한 프로젝트 결정

Decision Ref Project Summary Branch Application Source
DEC-KEYCLOAK-PATTERNS-OVERVIEW-SPA-CLIENT-001@1 AP1은 public client와 Authorization Code + PKCE를 사용한다 public SPA의 refresh rotation과 logout 흐름에 적용한다 raw/project-notes/keycloak-patterns-overview
DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1 done-bar는 E2E success와 signature security failure 재현·해결 evidence다 rotation·logout 후 session과 token 무효화 검증 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

없음.

목표

raw/branch-notes/feature-keycloak-refresh-token-rotation D1/D4가 정의한 rotation 정책·검증 계약을 target Keycloak 버전에서 실행한다. Max Reuse=0/1의 실제 의미와 RT 재사용 후 영향 범위를 관찰하고, logout/revoke 흐름과 JWT의 stateless 한계도 시연한다.

면접 질문: "JWT를 즉시 무효화할 수 있나요?" → "self-contained access token을 로컬 검증하면 revoke 결과가 즉시 반영되지 않을 수 있어 짧은 TTL을 사용합니다. refresh token rotation은 사용된 RT를 무효화하고 새 RT를 발급하지만, 예전 RT 재사용 시 후속 RT나 session까지 어떻게 영향받는지는 Keycloak 버전별 실험으로 확인해야 합니다. 초 단위 무효화가 필요하면 introspection 같은 stateful 검증의 비용을 별도로 평가합니다."

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

범위

포함 범위

  • concept owner의 candidate profile을 소비해 Revoke Refresh Token = ONRefresh Token Max Reuse = 0/1을 각각 설정·비교
  • SPA가 silent renew 호출 시마다 새 refresh token 받는 것 확인 (DevTools)
  • 동일 refresh token 2회 사용 시도 후 RT_1 응답, RT_2 후속 사용, realm session 상태를 분리 관찰
  • /protocol/openid-connect/revoke 엔드포인트 호출 (refresh token revoke)
  • /protocol/openid-connect/logout?id_token_hint=...&post_logout_redirect_uri=... 흐름
  • 함정 시연: access token revoke 즉시 적용 안 됨 (다음 만료까지 유효)
  • 짧은 access token 만료(5분)의 트레이드오프 측정
  • backend가 iat/exp 확인하는 방식 (Spring 기본 동작)

제외 범위

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

  • backend가 매 요청 introspection 호출 (stateless 포기 패턴)
  • Keycloak event listener / custom SPI
  • distributed token blacklist 캐시 (Redis 등)
  • back-channel logout receiver 구현과 provider-trigger E2E — 현재 문서에서는 옵션 시연도 하지 않으며 endpoint를 가정하지 않음. 필요 시 전용 branch 신설

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

이 branch의 구현·설계 결정의 근거가 되는 외부 자료. 같은 자료가 여러 결정의 근거면 결정 표시와 함께 여러 번 등장 가능. Claim ID 는 각 raw 의 ## Claims Extracted 표에서 안정적으로 유지.

Source 정당화하는 결정 (Claim)
raw/official-docs/oauth2-token-revocation-rfc-7009 D4 revoke 계약(RFC7009-C1~C3: endpoint·token·token_type_hint 파라미터), D4 refresh revoke 시 관련 access token SHOULD 무효화(RFC7009-C4), D3 stateless JWT trap 의 표준 원인(RFC7009-C5: self-contained AT → RS 추가 상호작용 불필요), D2 짧은 TTL 이 RFC 자신이 제시하는 설계 대안(RFC7009-C6, 방향성만·수치 미권고)
raw/official-docs/keycloak-oidc-logout-endpoint-official D4 logout 흐름 — RP-Initiated Logout endpoint(KC-LOGOUT-C1/C2), id_token_hint 미전달 시 confirm(C3), post_logout_redirect_uri auto redirect + 필수 동반 파라미터(C4/C5), Valid Post Logout Redirect URIs 매칭 검증(C6), Backchannel Logout URL(C7)
raw/official-docs/keycloak-refresh-token-rotation-reuse-admin-official D1/D5 "Revoke Refresh Token" 토글 = rotation(사용된 RT 무효화 + 새 토큰 발급) 공식 정의(KC-ROT-C1), D2 "Access Token Lifespan" 설정(C3) + 짧은 lifespan = 유출 완화 공식 원칙(C5). ⚠️ "Refresh Token Max Reuse" 설정명 + "재사용 시 family invalidate" 자동 동작은 이 공식 문서(v26.7.0)에 부재함을 전수 검색으로 확인(KC-ROT-C6, negative finding) → D1/D5 의 그 부분은 UNSUPPORTED_DECISION 유지
raw/official-docs/security-jwt-rfc-7519-validation D3 exp 시각 도달 이후 JWT MUST NOT be accepted(JWT-RFC7519-C2) — "revoke 직후엔 200, 만료 후 401" 함정의 표준 근거
raw/official-docs/spring-security-resource-server-jwt D3 backend(Spring RS)가 issuer-uri 로 self-config + JWKS 서명/iss/exp 만 검증하고 매 요청 introspection 안 함(SSRS-JWT-C1/C2) — self-contained 검증 구성 확인
raw/official-docs/oidc-client-ts-library D5 SPA silent renew 메커니즘 — Refresh Token Grant(OIDCTS-C4) + Silent Refresh in iframe(OIDCTS-C5). signoutRedirect()/logout redirect(라이브러리 API, needs-confirmation)
raw/official-docs/oauth-v2-1-draft-ietf D1/D5 rotation 권고 배경 — refresh token MUST be bound to scope/resource server(OA21-C3), code grant 가 AT+RT 발급 표준 경로(OA21-C6)
raw/official-docs/keycloak-securing-apps-overview-official (일반 배경) Keycloak 통합 시 표준 protocol 우선 / adapter 는 last resort(KC-SECAPP-C1/C2). rotation/revoke/logout 구체 동작의 verbatim 은 이 overview 에 없음 — 위 전용 raw 들이 대체

TODO

각 항목 옆에 증거 등급 표기: actually-implemented | locally-verified | prod-verified | documented-only | planned | needs-confirmation

  • raw/branch-notes/feature-keycloak-refresh-token-rotation D1 profile을 소비해 Revoke Refresh Token = ON에서 Refresh Token Max Reuse = 01을 각각 설정; UI/export key도 기록 — 등급: planned
  • Keycloak admin: Access Token Lifespan 5분 (짧게) — 등급: planned
  • SPA login → access_token / refresh_token 1 발급 — 등급: planned
  • silent renew 호출 1회 → 새 access_token + 새 refresh_token 2 수신 확인 → refresh_token 1 invalidate — 등급: planned
  • reuse 관찰 시연: refresh_token 1을 다시 사용한 응답 status/body를 기록하고, refresh_token 2의 후속 사용과 realm session 상태를 별도로 확인. 0/1 profile 결과를 비교하며 family invalidation을 expected result로 두지 않음 — 등급: planned
  • /protocol/openid-connect/revoke 엔드포인트로 refresh_token 명시적 revoke (curl) — 등급: planned
  • 함정 시연: revoke 직후 동일 access_token으로 /api/me 호출 → 200 OK (만료 전이므로 유효) → 5분 뒤 호출 → 401 — 등급: planned
  • DevTools Network 탭에서 /token (grant_type=refresh_token) 호출 시 응답 body 캡처 (rotation 전후 refresh_token 값 비교) — 등급: planned
  • SPA logout button → userManager.signoutRedirect() → Keycloak /logout?id_token_hint=...&post_logout_redirect_uri=http://localhost/ 호출 확인 — 등급: planned
  • logout 후 Keycloak session 종료 → SPA /api/me 호출 시 access token 만료 전이면 여전히 200 → 함정 재확인 — 등급: planned
  • logout 후 brower에서 Keycloak 다시 접근 시 SSO session 없어 재로그인 필요 확인 — 등급: planned
  • backend에서 exp claim 만료 시 401 응답 코드 확인 (Spring 기본 동작 검증) — 등급: planned
  • 트레이드오프 정리 노트: "stateless JWT vs 즉시 무효화" — 등급: planned

진행 중 메모

  • Refresh Token Rotation의 확인된 동작: 사용된 refresh token을 무효화하고 새 refresh token을 발급한다. 예전 RT의 재등장은 탈취뿐 아니라 client race/retry일 수도 있으므로 원인을 단정하지 않는다.
  • Max Reuse 의미는 실험 대상: 0과 1에서 같은 sequence를 실행하고 응답·후속 RT·session 상태를 비교한다. race가 결과를 섞지 않도록 시연 중 단일 refresh thread를 보장한다.
  • access token revoke 즉시 적용 안 되는 이유: backend가 매 요청마다 Keycloak에 introspection 안 함 — JWT signature/iss/aud/exp만 검증. 그게 JWT의 본질적 트레이드오프.
  • 짧은 access token 만료: 5분으로 줄이면 revoke 후 최대 5분 노출 — 학습 단계 권장. prod는 115분 권장 (보안 vs Keycloak 부하 트레이드오프).
  • id_token_hint의 역할: logout 시 어느 session을 끝낼지 식별. ID Token이 없으면 Keycloak이 logout 페이지에서 "정말 로그아웃?" 추가 확인 UI 표시.
  • post_logout_redirect_uri: Keycloak client 설정에 Valid post logout redirect URIs로 사전 등록 필요 (현재 Keycloak 18+).

결정 사항 (decisions)

  • 2026-07-18: 본 branch의 D1은 보안 정책 결정이 아니라 실행 test profile이다. raw/branch-notes/feature-keycloak-refresh-token-rotation D1을 소비해 rotation ON에서 Max Reuse 0/1을 모두 관찰하며 값 의미를 미리 정하지 않는다.
  • 2026-05-25: Access Token Lifespan 5분. 이유: revoke 함정을 짧은 대기 시간으로 검증 가능.
  • 2026-05-25: introspection 패턴은 out of scope. 이유: JWT stateless를 포기하는 트레이드오프 — 학습 목적은 stateless의 한계를 인지하는 것.
  • 2026-05-25: 명시적 revoke + logout 분리 학습. 이유: 두 흐름이 다른 endpoint를 사용하는 것을 직접 확인.

결정-근거 매핑

2026-07-18 /branch-spec 자동조사 반영: RFC 7009(revoke) + Keycloak logout endpoint + Keycloak "Revoke Refresh Token" 설정 정의 + RFC 7519 exp + Spring RS + oidc-client-ts + OAuth 2.1 raw 를 Sources 에 연결. 결과 — D3·D4 는 official 근거로 완전 해소, D1·D2·D5 는 부분 해소(방향/메커니즘 확보, 잔여 UNSUPPORTED). 잔여 UNSUPPORTED 는 근거 부족이 아니라 공식 문서에 없음을 전수 검색으로 확정한 gap(Keycloak "Refresh Token Max Reuse" 설정명 + family-invalidate 자동 동작 = KC-ROT-C6 negative finding) 또는 어느 표준도 권고 안 하는 임의 수치("5분")다. 둘 다 admin UI 실측/실험으로만 닫힌다 → ## Claims To Verify 참조. Decision ID 는 이 branch-note 안에서 안정적으로 유지. Supporting Claimsraw/<category>/<slug>.md#Cn 형식.

Decision ID Decision 선택 조건 (언제 이 결정 / 언제 대안) Supporting Claims Evidence Strength Open Risk
D1 실행 test profile — concept owner raw/branch-notes/feature-keycloak-refresh-token-rotation D1을 소비해 rotation ON + Max Reuse 0/1을 동일 조건에서 비교 target-version 의미를 검증할 때 두 profile 모두 실행. 한 값을 보안상 우월하다고 사전 분류하지 않음 raw/official-docs/keycloak-refresh-token-rotation-reuse-admin-official.md#KC-ROT-C1 + raw/official-docs/oauth-v2-1-draft-ietf.md#OA21-C3 + UNSUPPORTED_DECISION(KC-ROT-C6) official-vendor-doc + official-standard + needs-confirmation (Max Reuse semantics) UI/export에서 필드 존재를 확인하고 실험 결과에 따라 concept owner를 갱신. 본 D1이 독립 정책 owner가 되지 않음
D2 Access Token Lifespan 5분 — revoke 함정 짧은 대기로 검증 학습 단계엔 5분(revoke 함정을 5분 대기로 관찰 가능). prod 는 보안 vs Keycloak 부하 균형으로 1~15분 구간에서 선택 raw/official-docs/keycloak-refresh-token-rotation-reuse-admin-official.md#KC-ROT-C3 (Access Token Lifespan 설정 존재/역할) + #KC-ROT-C5 (짧은 lifespan = 유출 완화 Keycloak 공식 원칙) + raw/official-docs/oauth2-token-revocation-rfc-7009.md#RFC7009-C6 (short-lived AT = RFC 설계 대안) + raw/official-docs/security-jwt-rfc-7519-validation.md#JWT-RFC7519-C2 (exp 후 MUST NOT accept — 만료 대기가 검증 방법인 이유). UNSUPPORTED_IMPL_DECISION: "5분" 정확한 수치는 어느 표준도 분 단위 미권고 (trade-off: 짧을수록 revoke 노출창↓ but refresh 왕복↑·서버 부하↑; 5분은 학습 대기시간 편의로 임의 선택) official-vendor-doc (KC-ROT-C3/C5 방향) + official-standard (RFC7009-C6, JWT-RFC7519-C2) + UNSUPPORTED_IMPL_DECISION (5분 수치) OWASP/Keycloak 공식 권장 TTL 구간 raw 추가 시 수치 보강
D3 introspection 패턴은 out of scope — JWT stateless 트레이드오프 학습 목적 stateless 한계 인지가 목표면 introspection out of scope. 진짜 즉시 무효화가 요구되면 대안: 매 요청 introspection 또는 opaque/reference token 채택(별도 branch, stateless 이점 포기) raw/official-docs/oauth2-token-revocation-rfc-7009.md#RFC7009-C5 (self-contained AT → RS 추가 상호작용 불필요 = revoke 즉시 반영 안 됨의 표준 원인) + raw/official-docs/security-jwt-rfc-7519-validation.md#JWT-RFC7519-C2 (exp 후 MUST NOT accept) + raw/official-docs/spring-security-resource-server-jwt.md#SSRS-JWT-C1 (Spring RS = signature/iss/JWKS 검증, 매 요청 introspection 안 함) official-standard (RFC7009-C5, JWT-RFC7519-C2) + official-vendor-doc (SSRS-JWT-C1/C2) Keycloak 이 기본 self-contained JWT access token 을 발급하는지 실측(token decode) — RFC/Spring 은 일반 아키텍처만 증명
D4 명시적 revoke + logout 분리 학습 — 두 endpoint 의 다른 동작 직접 확인 특정 토큰만 즉시 폐기(SSO session 유지 가능)면 /revoke. 사용자 로그아웃(브라우저 SSO session 종료)까지면 /logout. 목적이 달라 분리 시연 (revoke) raw/official-docs/oauth2-token-revocation-rfc-7009.md#RFC7009-C1C4 (endpoint·token·token_type_hint·refresh revoke SHOULD cascade AT) + (logout) raw/official-docs/keycloak-oidc-logout-endpoint-official.md#KC-LOGOUT-C1C6 (logout endpoint·RP-initiated redirect·id_token_hint·post_logout_redirect_uri·Valid Post Logout Redirect URIs 매칭) official-standard (RFC 7009: RFC7009-C1~C4) + official-vendor-doc (Keycloak logout: KC-LOGOUT-C1~C6) Keycloak 세션이 /logout 호출 후 실제로 종료되는지 실측(runtime) — 명세는 확보, 동작은 미검증 (Claims To Verify #3)
D5 실행 관찰 절차 — concept owner raw/branch-notes/feature-keycloak-refresh-token-rotation D4를 소비해 RT_1 사용→AT_2+RT_2 발급→RT_1 재사용 응답→RT_2 후속 사용→realm session 상태를 순서대로 기록 D1의 0/1 profile 각각에 동일 절차 적용. family invalidation은 가능한 관찰 결과 중 하나일 뿐 expected result가 아님 raw/official-docs/keycloak-refresh-token-rotation-reuse-admin-official.md#KC-ROT-C1, raw/official-docs/oauth-v2-1-draft-ietf.md#OA21-C3, #OA21-C6, raw/official-docs/oidc-client-ts-library.md#OIDCTS-C4, #OIDCTS-C5 + UNSUPPORTED_DECISION(KC-ROT-C6) official-vendor-doc + official-standard + needs-confirmation (reuse impact) status/body, RT_2 유효성, session 상태를 독립 증거로 남기고 concept owner D4에 결과 반영

구현 가이드

결정이 "무엇"이라면 본 §는 "어디에 어떻게"의 사전 명세. 본 branch 는 코드베이스가 아니라 Keycloak admin 설정 + curl/DevTools 실험 이 "구현"이므로, sub-section 은 설정 카탈로그·endpoint 계약·실험 시퀀스로 구성한다. ⚠️ 실 구현 repo /home/donghyeon/workspace/keycloak-patterns/ 는 현재 미생성(controller 확인) → 아래 모든 항목은 planned, 코드 존재 주장 없음.

1. Keycloak Realm Token/Session 설정 카탈로그 (D1·D2)

Trace: D1(concept owner의 0/1 test profile) + D2(Access Token Lifespan 5분). 근거: KC-ROT-C1(Revoke Refresh Token 정의)·KC-ROT-C3(Access Token Lifespan 정의).

  • UNSUPPORTED_IMPL_DECISION:
    • Refresh Token Max Reuse 필드명과 0/1 의미 — KC-ROT-C6(공식 문서 부재). admin UI/export 및 runtime 비교 전까지 실험 변수로만 취급한다.
    • Access Token Lifespan = 5분 수치 — 표준 미권고(D2 trade-off). 학습 대기시간 편의로 임의 선택.
위치 (admin console) 설정 근거
Realm Settings → Sessions/Tokens Revoke Refresh Token ON KC-ROT-C1 (Enabled → 사용된 RT revoke + 새 토큰 발급)
Realm Settings → Sessions/Tokens Refresh Token Max Reuse 0, 1 두 profile UNSUPPORTED_IMPL_DECISION (KC-ROT-C6 부재 — UI/export + runtime 비교)
Realm Settings → Sessions/Tokens Access Token Lifespan 5m KC-ROT-C3 (설정 역할) + UNSUPPORTED_IMPL_DECISION (수치)
Client(spa-client) → Logout settings Valid Post Logout Redirect URIs http://localhost/ 등록 KC-LOGOUT-C6 (매칭 필수) — §3 참조

2. Rotation reuse-detection 시연 시퀀스 (D5)

Trace: D5(rotation flow). 근거: KC-ROT-C1(rotation 동작) + OIDCTS-C4/C5(SPA silent renew) + OA21-C3/C6(배경).

  • 관찰 경계: RT_1 재사용 후 family 전체 invalidation은 문서로 증명되지 않았다(KC-ROT-C6). 따라서 시퀀스는 expected result가 아니라 응답·RT_2·session을 분리 측정하는 절차다.
1) SPA login (oidc-client-ts UserManager.signinRedirect) → AT_1 + RT_1 발급        [OIDCTS-C3/C4]
2) silent renew 1회 (startSilentRenew) → /token grant_type=refresh_token(RT_1)
   → Keycloak: RT_1 revoke + AT_2 + RT_2 반환                                       [KC-ROT-C1]
3) stolen token 시뮬레이션: curl 로 RT_1 재사용 → /token(RT_1)
   → 관찰 A: HTTP status/body                                                       [UNSUPPORTED — KC-ROT-C6]
4) RT_2 로 다음 refresh 수행 → 관찰 B: 성공/실패와 응답
5) realm session 상태 확인 → 관찰 C: session 유지/종료
  • DevTools Network 탭: 2)의 /token 응답 body 에서 refresh_token 값이 RT_1→RT_2 로 바뀌는지 캡처(rotation 증거).
  • ⚠️ silent renew와 manual curl이 동시에 RT_1을 쓰면 어떤 호출이 먼저 소비했는지 불명확해진다. manual 시연 시 silent renew를 일시 중단하고 순서를 로그 timestamp로 고정한다.

3. 명시적 revoke + RP-Initiated Logout endpoint 계약 (D4)

Trace: D4(revoke/logout 분리). 근거: revoke = RFC7009-C1~C4, logout = KC-LOGOUT-C1~C6. UNSUPPORTED 없음(양 endpoint 모두 official 근거 확보).

흐름 요청 파라미터 계약 근거
refresh token revoke POST /realms/<realm>/protocol/openid-connect/revoke token=<RT> (REQUIRED) · token_type_hint=refresh_token (OPTIONAL) · client_id=<spa-client> RFC7009-C1/C2/C3
revoke 부수효과 (위 동일) RT revoke 시 동일 grant 의 access token 도 SHOULD 무효화(AS 지원 시) — MUST 아님 RFC7009-C4
RP-Initiated Logout GET /realms/<realm>/protocol/openid-connect/logout?id_token_hint=<id_token>&post_logout_redirect_uri=http://localhost/ id_token_hint 없으면 confirm UI(C3) · post_logout_redirect_uri 쓰려면 client_id 또는 id_token_hint 동반(C5) · 값은 Valid Post Logout Redirect URIs 와 매칭(C6) KC-LOGOUT-C1~C6
SPA 트리거 UserManager.signoutRedirect() 라이브러리가 위 logout URL 구성 OIDCTS (API needs-confirmation)

4. Stateless JWT trap 검증 절차 (D3)

Trace: D3(stateless 한계 학습). 근거: RFC7009-C5(self-contained AT) + JWT-RFC7519-C2(exp 후 MUST NOT accept) + SSRS-JWT-C1/C2(Spring RS 검증 구성). UNSUPPORTED 없음.

1) /protocol/openid-connect/revoke 로 RT revoke (또는 logout)
2) 동일 AT 로 backend GET /api/me 즉시 호출 → 기대: 200 OK
   (AT 만료 전 · Spring RS 는 서명/iss/exp 만 검증, revoke 사실 모름)              [RFC7009-C5, SSRS-JWT-C1]
3) Access Token Lifespan(5분) 경과 후 재호출 → 기대: 401
   (exp 도달 → MUST NOT be accepted)                                              [JWT-RFC7519-C2]

엣지·실패·의존

R4 캡처용. 정상 경로 외에 구현(시연) 중 부딪힐 실패/엣지 + 다른 계약 의존.

  • 실패·엣지 경로:
    • SPA race로 관찰 오염: silent renew와 manual /token이 동시에 RT_1을 제출하면 누가 토큰을 먼저 소비했는지 알 수 없다. 시연 시 silent renew를 끄거나 in-flight refresh를 단일 promise로 직렬화한다.
    • post_logout_redirect_uri 미등록: client Valid Post Logout Redirect URIs 에 없으면 매칭 실패(KC-LOGOUT-C6) → logout 거부/에러. 기대: 사전 등록(§구현 가이드 1, raw/branch-notes/feature-keycloak-realm-client-export 의존).
    • id_token_hint 누락: post_logout_redirect_uri 만 주고 id_token_hint/client_id 둘 다 없으면 confirm UI 노출(KC-LOGOUT-C3/C5) → 자동 redirect 안 됨. 기대: id_token_hint 동반.
    • revoke 후 만료 전 AT = 여전히 200 (함정 그 자체): RFC7009-C5 로 표준상 예상되는 결과. "버그"가 아니라 stateless 아키텍처의 정상 동작 — 학습 포인트.
    • Keycloak iss/issuer-uri 불일치: KC_HOSTNAME 과 backend issuer-uri 가 다르면 JWT iss 검증 실패로 revoke/logout 시연 이전에 401(SSRS-JWT-C1).
  • 다른 계약 의존 (sibling branch + 그 Decision ID — 소비하는 계약이 어느 결정에서 확정됐는지):

검증해야 할 주장

구현 전/중/후에 실제 검증해야 하는 주장. P3A는 실 구현 대상이며, D1/D5의 0/1 비교 결과를 concept owner에 환류한다.

Claim Why uncertain How to verify Status
Max Reuse=01에서 RT_1 재사용이 RT_2와 realm session에 미치는 영향 필드·값 의미와 family invalidation 동작의 verbatim 인용 없음(KC-ROT-C6); 버전별 차이 가능 두 profile에서 동일하게 RT_1 사용→RT_2 발급→RT_1 재사용→RT_2 사용→session 확인. 각 status/body를 독립 기록 planned
/protocol/openid-connect/revoke 로 RT revoke 후 동일 access_token 으로 /api/me 호출 시 만료 전에는 200 응답 (stateless JWT 한계) JWT stateless backend 동작의 표준 근거는 확보(RFC7009-C5)나 Keycloak+Spring 실동작 미검증 Access Token Lifespan 5분 설정 → revoke 직후 호출 (200 예상) → 5분 후 호출 (401 예상) planned
Keycloak /protocol/openid-connect/logout?id_token_hint=...&post_logout_redirect_uri=... 흐름이 OIDC RP-Initiated Logout 1.0 을 준수 spec 근거는 확보(KC-LOGOUT-C1~C6)나 Keycloak 세션이 실제 종료되는지 runtime 미검증 Keycloak admin UI 의 logout endpoint 동작 캡처 + logout 후 SSO session 없어 재로그인 필요 확인 needs-confirmation
post_logout_redirect_uri 가 Keycloak client Valid post logout redirect URIs 에 사전 등록 필요 근거 확보(KC-LOGOUT-C6); 내 client 설정에서 실제 매칭·거부 미확인 Keycloak admin UI 에서 client 설정 캡처 + 미등록 URI 로 logout 시 거부 확인 needs-confirmation
oidc-client-ts silent renew와 manual /token 호출이 겹칠 때 실험 순서가 오염되는지 silent renew 존재는 확보했지만 동시 호출 순서와 target Keycloak 결과는 미검증 timestamp와 Keycloak log로 두 요청 순서를 기록하고, 정식 0/1 비교 실험은 silent renew OFF로 재실행 planned
Spring Security Resource Server 가 exp claim 만료 시 401 응답 (basic JWT 검증 동작) 표준(JWT-RFC7519-C2)+Spring 구성(SSRS-JWT-C1/C2) 근거 확보나 실제 401 응답 미확인 5분 후 호출 시 401 응답 캡처 planned

관심사 커버리지

EXEMPTrules/coverage-gate.md §7: governing_docs 미지정 + related_projects 에 ca-skeleton/ca-tmpl 없음(= [keycloak-patterns] 학습 노트) → coverage 게이트 면제. 완전성 기준(governing canonical 문서)이 존재하지 않으므로 관심사 매트릭스를 생성하지 않는다. 깊이(depth R1~R4)만 게이트 대상.

마주친 문제

  • (구현 시작 후 추가) post_logout_redirect_uri가 client에 등록 안 되어 logout 실패 예상 — sub-5-2에서 추가 등록 필요.
  • (구현 시작 후 추가) oidc-client-ts silent renew와 manual refresh token 호출이 충돌 가능 — manual 시연 시 silent renew 일시 OFF.
  • (구현 시작 후 추가) refresh token 요청 race가 0/1 비교 결과를 오염할 가능성 — DevTools와 Keycloak log에서 호출 순서를 확인.

묶음

본 sub-sub-branch 는 leaf — 자식 자료 없음. Phase 3 P3A 실 구현 또는 외부 산출물 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.

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

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

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

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

관련 일일 노트

완료 후 정리

rotation 재사용 시연, revoke 함정 시연, logout 전체 흐름이 로그/캡처로 증명되면 plannedactually-implemented/locally-verified 승급. 트레이드오프 노트는 향후 wiki/concepts/로 ingest 후보.

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