The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.
Follows the import procedure in README.md.
source/ the originating repository verbatim — 78 documents, 28 SVGs,
8 manifests, plus .source-revision recording the commit
final/ the SSOT
document.md 729 lines written from the 29 experiment documents, not
concatenated: what was predicted, what was measured, and
where the measurement itself was wrong
evidence/raw 125 outputs, flattened to <experiment>__<file> because
the originals collided (01-baseline.txt appeared three
times) and the audit only globs the top level
evidence/meta one per raw file; command and exitCode are null and the
README says why rather than inventing them
evidence/browser 22 captures
assets/ three diagrams through techviz
.techviz/ their VizSpecs
A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.
Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.
verify-pipeline.py passes. audit-records.py reports no issues.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
399 lines
19 KiB
JSON
399 lines
19 KiB
JSON
{
|
|
"schemaVersion": 2,
|
|
"project": "keycloak",
|
|
"ssot": "final/document.md",
|
|
"ssotSha256": "0625bc875ab31ca6f331e6f64bd93f26397d54f3ac6e0162f4128a53a1d97e2d",
|
|
"generatedAt": "2026-09-04",
|
|
"note": "글감 목록이다. file 이 있으면 이미 쓴 기록이고, 없으면 아직 쓰지 않은 글감이다. ssotSha256 이 지금 final/document.md 와 다르면 SSOT 가 바뀐 뒤 트리를 다시 보지 않은 것이다.",
|
|
"readinessValues": [
|
|
"READY",
|
|
"NEEDS_EVIDENCE",
|
|
"BLOCKED",
|
|
"REJECTED"
|
|
],
|
|
"topics": {
|
|
"oauth-oidc-auth-boundary": {
|
|
"topic": "oauth-oidc-auth-boundary",
|
|
"kinds": {
|
|
"case": [
|
|
{
|
|
"title": "Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출",
|
|
"slug": "split-custody-access-token",
|
|
"file": "oauth-oidc-auth-boundary/case/case-ap2-split-custody.md",
|
|
"readiness": "READY",
|
|
"status": "게시 중",
|
|
"studioId": "488ce49b-afa4-42a5-a2ce-de2e0653cd82",
|
|
"assets": [
|
|
"ap2-split-custody-779cb791"
|
|
],
|
|
"evidence": [],
|
|
"relations": [
|
|
"SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계",
|
|
"Public Client와 Confidential Client 구분 기준",
|
|
"OAuth Token과 Application Session을 구분하는 기준",
|
|
"OAuth/OIDC 인증 패턴 선택 기준",
|
|
"Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가"
|
|
]
|
|
},
|
|
{
|
|
"title": "BFF에서 Browser Token을 제거하고 Session과 CSRF를 처리한 방식",
|
|
"slug": "bff-session-csrf-responsibility",
|
|
"file": "oauth-oidc-auth-boundary/case/case-ap3-bff-session-csrf.md",
|
|
"readiness": "READY",
|
|
"status": "게시 중",
|
|
"studioId": "d85bd6af-7599-4ef7-9407-6609927d5b5c",
|
|
"assets": [
|
|
"ap3-bff-custody-82fa18bd",
|
|
"ap3-csrf-split-501dd1f7"
|
|
],
|
|
"evidence": [],
|
|
"relations": [
|
|
"BFF 인증 구조 설계 기준",
|
|
"OAuth Token과 Application Session을 구분하는 기준",
|
|
"OAuth/OIDC 인증 패턴 선택 기준",
|
|
"BFF가 OAuth Token을 관리하는 조건",
|
|
"서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가",
|
|
"BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가"
|
|
]
|
|
},
|
|
{
|
|
"title": "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유",
|
|
"slug": "identity-header-trust",
|
|
"file": "oauth-oidc-auth-boundary/case/case-ap4-identity-header-trust.md",
|
|
"readiness": "READY",
|
|
"status": "게시 중",
|
|
"studioId": "a0e1cc05-92b3-4dac-bce1-513ab8cd862b",
|
|
"assets": [
|
|
"ap4-edge-trust-1cff2399"
|
|
],
|
|
"evidence": [],
|
|
"relations": [
|
|
"Forward-Auth에서 Identity Header를 신뢰하기 위한 조건",
|
|
"OAuth Token과 Application Session을 구분하는 기준",
|
|
"OAuth/OIDC 인증 패턴 선택 기준",
|
|
"Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가"
|
|
]
|
|
},
|
|
{
|
|
"title": "SPA에서 OAuth Token을 JavaScript Memory에 보관한 경우",
|
|
"slug": "spa-browser-credential-boundary",
|
|
"file": "oauth-oidc-auth-boundary/case/case-browser-credential-boundary.md",
|
|
"readiness": "READY",
|
|
"status": "게시 중",
|
|
"studioId": "bf675775-4f3e-4744-8014-f0efff51422a",
|
|
"assets": [
|
|
"ap1-custody-v3-6e0376d2"
|
|
],
|
|
"evidence": [],
|
|
"relations": [
|
|
"Authorization Code Flow의 Endpoint와 Credential 이동 기준",
|
|
"Public Client와 Confidential Client 구분 기준",
|
|
"OAuth Token과 Application Session을 구분하는 기준"
|
|
]
|
|
}
|
|
],
|
|
"concept": [
|
|
{
|
|
"title": "Authorization Code와 PKCE가 보호하는 구간",
|
|
"slug": "authorization-code-and-pkce",
|
|
"file": "oauth-oidc-auth-boundary/concept/concept-authorization-code-and-pkce.md",
|
|
"readiness": "READY",
|
|
"status": "게시 전",
|
|
"studioId": "75c6c657-3e03-47a0-a9d0-5637fce9dd3f",
|
|
"assets": [],
|
|
"evidence": [],
|
|
"relations": [
|
|
"Authorization Code Flow의 Endpoint와 Credential 이동 기준",
|
|
"Public Client와 Confidential Client 구분 기준",
|
|
"SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계"
|
|
]
|
|
},
|
|
{
|
|
"title": "Bearer JWT가 인증된 principal이 되기까지",
|
|
"slug": "bearer-jwt-validation-chain",
|
|
"file": "oauth-oidc-auth-boundary/concept/concept-bearer-jwt-validation-chain.md",
|
|
"readiness": "READY",
|
|
"status": "게시 전",
|
|
"studioId": "87000d59-b69f-4010-9481-0b71c8bde32d",
|
|
"assets": [],
|
|
"evidence": [],
|
|
"relations": [
|
|
"OAuth Token과 Application Session을 구분하는 기준",
|
|
"Authorization Code Flow의 Endpoint와 Credential 이동 기준",
|
|
"SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계"
|
|
]
|
|
},
|
|
{
|
|
"title": "브라우저가 credential을 보관하는 위치와 그 성질",
|
|
"slug": "browser-credential-storage",
|
|
"file": "oauth-oidc-auth-boundary/concept/concept-browser-credential-storage.md",
|
|
"readiness": "READY",
|
|
"status": "게시 전",
|
|
"studioId": "bb5c37ae-2d94-48f7-ad4e-a37c61c3fd07",
|
|
"assets": [],
|
|
"evidence": [],
|
|
"relations": [
|
|
"OAuth Token과 Application Session을 구분하는 기준",
|
|
"SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계",
|
|
"BFF 인증 구조 설계 기준"
|
|
]
|
|
},
|
|
{
|
|
"title": "Cookie로 인증하는 요청에서 CSRF token이 하는 일",
|
|
"slug": "cookie-auth-csrf",
|
|
"file": "oauth-oidc-auth-boundary/concept/concept-cookie-auth-csrf.md",
|
|
"readiness": "READY",
|
|
"status": "게시 전",
|
|
"studioId": "5c8f12d5-1ead-469b-8e91-2de69401df48",
|
|
"assets": [],
|
|
"evidence": [],
|
|
"relations": [
|
|
"BFF 인증 구조 설계 기준",
|
|
"Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정",
|
|
"OAuth Token과 Application Session을 구분하는 기준"
|
|
]
|
|
},
|
|
{
|
|
"title": "Forward-Auth와 Nginx auth_request의 동작",
|
|
"slug": "forward-auth-and-auth-request",
|
|
"file": "oauth-oidc-auth-boundary/concept/concept-forward-auth-and-auth-request.md",
|
|
"readiness": "READY",
|
|
"status": "게시 전",
|
|
"studioId": "a3493786-d3fb-4b01-b1c5-ecb23c3d5497",
|
|
"assets": [],
|
|
"evidence": [],
|
|
"relations": [
|
|
"Forward-Auth에서 Identity Header를 신뢰하기 위한 조건",
|
|
"Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유",
|
|
"OAuth/OIDC 인증 패턴 선택 기준"
|
|
]
|
|
},
|
|
{
|
|
"title": "외부 IdP Brokering의 동작",
|
|
"slug": "idp-brokering",
|
|
"file": "oauth-oidc-auth-boundary/concept/concept-idp-brokering.md",
|
|
"readiness": "READY",
|
|
"status": "게시 전",
|
|
"studioId": "d99fdec9-fe9e-4e0f-a50b-6fb9b9ed5719",
|
|
"assets": [],
|
|
"evidence": [],
|
|
"relations": [
|
|
"외부 IdP 연동과 Application 인증 구조의 경계",
|
|
"OAuth Token과 Application Session을 구분하는 기준",
|
|
"Authorization Code Flow의 Endpoint와 Credential 이동 기준"
|
|
]
|
|
}
|
|
],
|
|
"reference": [
|
|
{
|
|
"title": "Authorization Code Flow의 Endpoint와 Credential 이동 기준",
|
|
"slug": "authorization-code-endpoint-credential-movement",
|
|
"file": "oauth-oidc-auth-boundary/reference/reference-authorization-code-endpoints.md",
|
|
"readiness": "READY",
|
|
"status": "게시 중",
|
|
"studioId": "39fdf472-82c4-43ed-abec-73de672f08ae",
|
|
"assets": [],
|
|
"evidence": [],
|
|
"relations": [
|
|
"SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계",
|
|
"Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출",
|
|
"Public Client와 Confidential Client 구분 기준"
|
|
]
|
|
},
|
|
{
|
|
"title": "BFF 인증 구조 설계 기준",
|
|
"slug": "bff-authentication-design-criteria",
|
|
"file": "oauth-oidc-auth-boundary/reference/reference-bff-auth-design.md",
|
|
"readiness": "READY",
|
|
"status": "게시 중",
|
|
"studioId": "97eddd97-1096-426a-a2c6-a6c5bf1cd09f",
|
|
"assets": [],
|
|
"evidence": [],
|
|
"relations": [
|
|
"Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정",
|
|
"서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가",
|
|
"BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가",
|
|
"BFF가 OAuth Token을 관리하는 조건"
|
|
]
|
|
},
|
|
{
|
|
"title": "Forward-Auth에서 Identity Header를 신뢰하기 위한 조건",
|
|
"slug": "forward-auth-identity-header-trust",
|
|
"file": "oauth-oidc-auth-boundary/reference/reference-forward-auth-header-trust.md",
|
|
"readiness": "READY",
|
|
"status": "게시 중",
|
|
"studioId": "004dd0a2-5fb3-4f25-80c9-576f709de331",
|
|
"assets": [],
|
|
"evidence": [],
|
|
"relations": [
|
|
"Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유",
|
|
"Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가",
|
|
"OAuth Token과 Application Session을 구분하는 기준"
|
|
]
|
|
},
|
|
{
|
|
"title": "외부 IdP 연동과 Application 인증 구조의 경계",
|
|
"slug": "external-idp-federation-application-boundary",
|
|
"file": "oauth-oidc-auth-boundary/reference/reference-idp-federation-boundary.md",
|
|
"readiness": "READY",
|
|
"status": "게시 중",
|
|
"studioId": "1a00a640-8987-4075-a9e4-7ec023cdffbb",
|
|
"assets": [],
|
|
"evidence": [],
|
|
"relations": [
|
|
"외부 IdP와의 연동이라도 별도의 인증 방식이 아니다.",
|
|
"SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계",
|
|
"Authorization Code Flow의 Endpoint와 Credential 이동 기준"
|
|
]
|
|
},
|
|
{
|
|
"title": "OAuth/OIDC 인증 패턴 선택 기준",
|
|
"slug": "oauth-oidc-pattern-selection-criteria",
|
|
"file": "oauth-oidc-auth-boundary/reference/reference-pattern-selection.md",
|
|
"readiness": "READY",
|
|
"status": "게시 중",
|
|
"studioId": "3f886154-1b85-407b-bda4-57d28370e745",
|
|
"assets": [],
|
|
"evidence": [],
|
|
"relations": [
|
|
"SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계",
|
|
"Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출",
|
|
"Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정",
|
|
"Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유"
|
|
]
|
|
},
|
|
{
|
|
"title": "Public Client와 Confidential Client 구분 기준",
|
|
"slug": "public-confidential-client-boundary",
|
|
"file": "oauth-oidc-auth-boundary/reference/reference-public-confidential-client.md",
|
|
"readiness": "READY",
|
|
"status": "게시 중",
|
|
"studioId": "ede6b9ce-eeed-40c8-9175-9e8116029395",
|
|
"assets": [],
|
|
"evidence": [],
|
|
"relations": [
|
|
"SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계",
|
|
"Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출",
|
|
"Authorization Code Flow의 Endpoint와 Credential 이동 기준"
|
|
]
|
|
},
|
|
{
|
|
"title": "OAuth Token과 Application Session을 구분하는 기준",
|
|
"slug": "oauth-token-application-session-boundary",
|
|
"file": "oauth-oidc-auth-boundary/reference/reference-token-vs-session.md",
|
|
"readiness": "READY",
|
|
"status": "게시 중",
|
|
"studioId": "66c18e42-116c-459f-86bd-b7e4bf394866",
|
|
"assets": [],
|
|
"evidence": [],
|
|
"relations": [
|
|
"SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계",
|
|
"Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출",
|
|
"Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정",
|
|
"Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유"
|
|
]
|
|
}
|
|
],
|
|
"question": [
|
|
{
|
|
"title": "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가",
|
|
"slug": "bff-session-authorized-client-store",
|
|
"file": "oauth-oidc-auth-boundary/question/question-bff-state-store.md",
|
|
"readiness": "READY",
|
|
"status": "게시 중",
|
|
"studioId": "18a5cde2-dd1e-4bff-9f1c-997577ae438f",
|
|
"assets": [],
|
|
"evidence": [],
|
|
"relations": [
|
|
"서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가",
|
|
"Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정",
|
|
"BFF 인증 구조 설계 기준",
|
|
"Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가"
|
|
]
|
|
},
|
|
{
|
|
"title": "Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가",
|
|
"slug": "edge-authorization-scope",
|
|
"file": "oauth-oidc-auth-boundary/question/question-edge-authorization-scope.md",
|
|
"readiness": "READY",
|
|
"status": "게시 중",
|
|
"studioId": "7ff40767-a00b-4db2-98f6-0cdfce8c8936",
|
|
"assets": [],
|
|
"evidence": [],
|
|
"relations": [
|
|
"Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유",
|
|
"Forward-Auth에서 Identity Header를 신뢰하기 위한 조건",
|
|
"BFF 인증 구조 설계 기준"
|
|
]
|
|
},
|
|
{
|
|
"title": "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가",
|
|
"slug": "server-session-pattern-multi-instance",
|
|
"file": "oauth-oidc-auth-boundary/question/question-multi-instance-session.md",
|
|
"readiness": "READY",
|
|
"status": "게시 중",
|
|
"studioId": "c72656b5-842d-45d9-b5f6-82b66b09d0b9",
|
|
"assets": [],
|
|
"evidence": [],
|
|
"relations": [
|
|
"Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정",
|
|
"Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출",
|
|
"BFF 인증 구조 설계 기준",
|
|
"BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가"
|
|
]
|
|
},
|
|
{
|
|
"title": "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가",
|
|
"slug": "refresh-rotation-replica-contention",
|
|
"file": "oauth-oidc-auth-boundary/question/question-refresh-rotation-replica.md",
|
|
"readiness": "READY",
|
|
"status": "게시 중",
|
|
"studioId": "9ae4ec71-a32e-49a7-88c2-f7368541c28d",
|
|
"assets": [],
|
|
"evidence": [],
|
|
"relations": [
|
|
"BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가",
|
|
"Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출",
|
|
"서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가",
|
|
"BFF 인증 구조 설계 기준"
|
|
]
|
|
}
|
|
],
|
|
"decision": [
|
|
{
|
|
"title": "BFF가 OAuth Token을 관리하는 조건",
|
|
"slug": "bff-owns-token-when-browser-must-not",
|
|
"file": "oauth-oidc-auth-boundary/decision/decision-bff-owns-token.md",
|
|
"readiness": "READY",
|
|
"status": "게시 중",
|
|
"studioId": "19b55c39-c583-4161-9775-df954280a568",
|
|
"assets": [],
|
|
"evidence": [],
|
|
"relations": [
|
|
"Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정",
|
|
"BFF 인증 구조 설계 기준",
|
|
"OAuth/OIDC 인증 패턴 선택 기준",
|
|
"Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출"
|
|
]
|
|
},
|
|
{
|
|
"title": "외부 IdP와의 연동이라도 별도의 인증 방식이 아니다.",
|
|
"slug": "federation-is-not-an-application-pattern",
|
|
"file": "oauth-oidc-auth-boundary/decision/decision-federation-not-a-pattern.md",
|
|
"readiness": "READY",
|
|
"status": "게시 중",
|
|
"studioId": "8c1ebea7-204e-445c-9812-0421d9eb0e9c",
|
|
"assets": [],
|
|
"evidence": [],
|
|
"relations": [
|
|
"외부 IdP 연동과 Application 인증 구조의 경계",
|
|
"SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계",
|
|
"OAuth Token과 Application Session을 구분하는 기준"
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|