30 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-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 |
|
|
1 | feature-keycloak-refresh-rotation-and-logout |
|
|
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 = ON과Refresh 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 = 0과1을 각각 설정; 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에서
expclaim 만료 시 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는 1–15분 권장 (보안 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 7519exp+ 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-C6negative finding) 또는 어느 표준도 권고 안 하는 임의 수치("5분")다. 둘 다 admin UI 실측/실험으로만 닫힌다 →## Claims To Verify참조.Decision ID는 이 branch-note 안에서 안정적으로 유지.Supporting Claims는raw/<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]
- backend 설정:
spring.security.oauth2.resourceserver.jwt.issuer-uri한 줄(=KC_HOSTNAME기반 issuer 와 정확 일치,SSRS-JWT-C1). issuer 불일치 시 401 — §엣지·의존 참조(raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch 의존).
엣지·실패·의존
R4 캡처용. 정상 경로 외에 구현(시연) 중 부딪힐 실패/엣지 + 다른 계약 의존.
- 실패·엣지 경로:
- SPA race로 관찰 오염: silent renew와 manual
/token이 동시에 RT_1을 제출하면 누가 토큰을 먼저 소비했는지 알 수 없다. 시연 시 silent renew를 끄거나 in-flight refresh를 단일 promise로 직렬화한다. post_logout_redirect_uri미등록: clientValid 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과 backendissuer-uri가 다르면 JWTiss검증 실패로 revoke/logout 시연 이전에 401(SSRS-JWT-C1).
- SPA race로 관찰 오염: silent renew와 manual
- 다른 계약 의존 (sibling branch + 그 Decision ID — 소비하는 계약이 어느 결정에서 확정됐는지):
- raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce
#D1·#D3— SPA 토큰 발급(#D1oidc-client-ts 채택) + silent renew(#D3automaticSilentRenew: true) 구현. 본 branch D5 시연이 이 SPA 흐름을 consume. 그 계약(로그인/갱신 방식)이 바뀌면 rotation 시연 절차 영향. - raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch
#D1·#D6—KC_HOSTNAME=localhost(#D1) + backendiss/issuer-uri문자열 일치 + JWKS 도달 메커니즘(#D6, ⚠️ 그 branch 기준 현재 기본=(C)). 본 branch D3 의 backend 401 검증(SSRS-JWT-C1) 전제. - raw/branch-notes/feature-keycloak-spring-rs-role-mapping
#D3— backend = stateless + JWT 만 사용(#D3)의 Resource Server 검증 경로. 본 branch D3 의exp→401 이 이 검증 체인 위에서 동작. - raw/branch-notes/feature-keycloak-docker-compose-stack
#D1·#D4— Keycloak 26.x 실행(#D1) + realm JSON auto-import(#D4). 모든 실측 TODO 의 실행 환경 전제. - raw/branch-notes/feature-keycloak-realm-client-export D5 — realm-export.json 저작·commit에 client
Valid Post Logout Redirect URIs등록 포함. 본 branch D4 logout의 전제. rotation 값·정책은 concept raw/branch-notes/feature-keycloak-refresh-token-rotation D1/D2가 owner이고 본 branch D1/D5는 실행만 담당한다.
- raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce
검증해야 할 주장
구현 전/중/후에 실제 검증해야 하는 주장. P3A는 실 구현 대상이며, D1/D5의 0/1 비교 결과를 concept owner에 환류한다.
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
Max Reuse=0과 1에서 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 |
관심사 커버리지
EXEMPT —
rules/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-tssilent renew와 manual refresh token 호출이 충돌 가능 — manual 시연 시 silent renew 일시 OFF. - (구현 시작 후 추가) refresh token 요청 race가 0/1 비교 결과를 오염할 가능성 — DevTools와 Keycloak log에서 호출 순서를 확인.
묶음
- raw/official-docs/keycloak-refresh-token-rotation-reuse-admin-official
- raw/official-docs/oauth2-token-revocation-rfc-7009
본 sub-sub-branch 는 leaf — 자식 자료 없음. Phase 3 P3A 실 구현 또는 외부 산출물 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.
오류 기록 (이 sub-sub-branch 작업 중 발생)
- (없음 — 현재 documented-only 단계)
면접 준비 (이 작업에서 나올 수 있는 면접 질문)
- (없음 — Phase 3 실 구현 단계에 누적)
관련 일일 노트
완료 후 정리
rotation 재사용 시연, revoke 함정 시연, logout 전체 흐름이 로그/캡처로 증명되면
planned→actually-implemented/locally-verified승급. 트레이드오프 노트는 향후wiki/concepts/로 ingest 후보.
- PR 링크: (별도 keycloak-patterns repo)
- 리뷰 메모:
- 머지 결과 / 배포 환경: 로컬 docker-compose / 단일 EC2 시뮬레이션
- wiki 추출 대상:
actually-implemented항목: (구현 후 채움)locally-verified항목: (구현 후 채움)prod-verified항목: (없음)
- 추출하지 않을 항목: 현재 전부
planned.