- 계약 채택 — 독자 질문, 후보 29건(PROMOTE 24 · MERGE_INTO 4 · KEEP_IN_SSOT 1). 게시 중 17건은 전부 유지. 저장소 keycloak-pattern 은 패턴 넷이 브랜치로 갈라져 있어 revisions 로 tip 넷을 적었다. keycloak-session-store 는 같은 저장소 @ cdac9b8 - 게시된 기록의 redirect_uri 가 SSOT·코드와 달랐다 — OAuth2callback.html → callback.html (frontend/src/app.js 에서 확인). 계약 title 이 기록과 다른 7건도 기록 쪽으로 맞췄다 - 미작성 1건 작성 — 패턴 검증을 실제로 돌릴 때의 안전한 순서(Reference) - 리뷰 100건 반영 — 설명 뒤에 붙은 평가·차례 예고·독자 오해 가정·작성 지시를 지웠다. 삭제가 남긴 조각 4건을 고치고, 원래부터 잘려 있던 로컬 미리보기 라벨 1건도 닫았다 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1058 lines
53 KiB
JSON
1058 lines
53 KiB
JSON
{
|
|
"schemaVersion": 4,
|
|
"project": "keycloak",
|
|
"ssot": "final/document.md",
|
|
"sourceRepository": {
|
|
"path": "/home/donghyeon/workspace/keycloak-pattern",
|
|
"revision": null,
|
|
"revisions": {
|
|
"AP1 develop-keycloak-pattern1": "64175266df05545f8f181fc91c1f0364bc47fce9",
|
|
"AP2 develop-keycloak-pattern2": "d019846f8725bdb0badde33043b020dc252e32ff",
|
|
"AP3 develop-keycloak-pattern3": "934c5da5d6edc2429dfb558b773656e46f21d677",
|
|
"AP4 develop-keycloak-pattern4": "f4aea65dc6255eae07b20ebbe21e02fb6115e563"
|
|
},
|
|
"verified": "네 패턴이 각각 다른 브랜치에 있어 단일 커밋으로 표현되지 않는다. 각 tip 에서 문서가 인용한 것을 찾아 대조했다 — pattern1 의 frontend/src/app.js 에 InMemoryWebStorage 와 redirect_uri http://localhost:8088/callback.html, pattern2 의 /token/access, pattern3 의 BffController /bff/token-boundary, pattern4 의 EdgeIdentityController /edge/me. 공통 realm(keycloak/import/keycloak-patterns-realm.json)의 client 넷과 mock-google-realm.json 도 확인했다. 저장소 HEAD 는 keycloak-session-store 작업이라 이 문서의 상태가 아니다 (2026-09-07 확인)"
|
|
},
|
|
"ssotSha256": "0625bc875ab31ca6f331e6f64bd93f26397d54f3ac6e0162f4128a53a1d97e2d",
|
|
"sourceRevision": "keycloak-patterns-lab@2026-08",
|
|
"generatedAt": "2026-09-07",
|
|
"candidateScope": {
|
|
"document": "final/document.md",
|
|
"sections": [
|
|
"문제를 어렵게 만든 제약",
|
|
"검토한 선택지와 막힌 지점",
|
|
"선택의 이유와 지킨 경계",
|
|
"선택이 코드와 흐름에 반영되는 방식",
|
|
"결정이 지켜지는지 확인하는 방법",
|
|
"얻은 것, 잃은 것, 적용하지 않을 때"
|
|
],
|
|
"excluded": [
|
|
"코드보다 먼저 드러난 문제",
|
|
"결국 지키려던 것은 무엇이었나"
|
|
],
|
|
"note": "처음부터 한 편으로 쓴 글이라 제2부가 없다. 여는 절은 문제 진술이고 맺음 절은 앞 절들의 종합이라 후보 자리가 아니다"
|
|
},
|
|
"note": "이 프로젝트의 글감 전부다. 분해 계약이자 색인이고, 이 파일이 정본이다. 노드의 칸은 사람이 적고 file·publication·status 는 기록 파일에서 읽어 채운다 — python3 scripts/build-tech-log-tree.py keycloak",
|
|
"contract": {
|
|
"decomposition": [
|
|
"글감을 찾는 입력은 final/document.md 하나다. 거기에 없는 근거는 먼저 SSOT 에 넣는다.",
|
|
"후보 전부는 candidates 에 처분과 함께 남고 PROMOTE 만 topics 로 올라간다.",
|
|
"없애고 관련 Case 나 Concept 의 한 절로 넣어도 이해·결정·재사용성이 그대로라면 독립 기록으로 만들지 않는다.",
|
|
"Topic 은 독자 질문 하나다. 그 물음에 답하지 않는 글감은 다른 Topic 으로 옮긴다.",
|
|
"Concept 은 Case·Decision·Question 을 먼저 고른 뒤 그것을 이해하는 데 필요한 것만 거꾸로 더한다."
|
|
],
|
|
"readinessValues": [
|
|
"READY",
|
|
"OPEN",
|
|
"NEEDS_EVIDENCE",
|
|
"NEEDS_DECISION",
|
|
"BLOCKED"
|
|
],
|
|
"dispositionValues": {
|
|
"PROMOTE": "독립 Tech Log 로 쓴다",
|
|
"MERGE_INTO": "다른 기록의 한 절로 흡수한다",
|
|
"KEEP_IN_SSOT": "분석에는 남기고 독립 기록으로 만들지 않는다 — 정상적인 성공 결과다",
|
|
"NEEDS_EVIDENCE": "주장에 아직 검증이 없다",
|
|
"NEEDS_DECISION": "방향이 그럴듯하지만 프로젝트가 정하지 않았다",
|
|
"BLOCKED": "원본이 불완전하거나 서로 어긋난다"
|
|
}
|
|
},
|
|
"topics": {
|
|
"oauth-oidc-auth-boundary": {
|
|
"topic": "oauth-oidc-auth-boundary",
|
|
"title": "OAuth 자격증명과 세션의 보관 경계",
|
|
"readerQuestion": "자격증명과 세션을 누가 보관하고, 누가 API 요청을 만들며, 보호 자원은 무엇을 신뢰하는가?",
|
|
"kinds": {
|
|
"case": [
|
|
{
|
|
"title": "SPA에서 OAuth Token을 JavaScript Memory에 보관한 경우",
|
|
"slug": "spa-browser-credential-boundary",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#검토한-선택지와-막힌-지점-ap1",
|
|
"final/document.md#선택의-이유와-지킨-경계-ap1",
|
|
"final/document.md#결정이-지켜지는지-확인하는-방법-ap1"
|
|
],
|
|
"code": [
|
|
"spa-public client",
|
|
"InMemoryWebStorage"
|
|
],
|
|
"classification": "한 패턴을 끝까지 따라가 토큰이 어디에 남는지 관측하고, 저장소를 줄여도 실행 중 XSS 권한은 줄지 않는다는 결론까지 닫았다",
|
|
"missing-verification": "silent renewal, SSO cookie flag, CORS preflight, callback error UX 는 이 검증 계약 밖이다",
|
|
"relations": [
|
|
"concept:browser-credential-storage",
|
|
"concept:authorization-code-and-pkce",
|
|
"concept:bearer-jwt-validation-chain",
|
|
"reference:public-confidential-client-boundary"
|
|
],
|
|
"kind": "case",
|
|
"publication": "게시됨",
|
|
"file": "oauth-oidc-auth-boundary/case/case-browser-credential-boundary.md",
|
|
"status": "게시 중",
|
|
"studioId": "bf675775-4f3e-4744-8014-f0efff51422a",
|
|
"assets": [
|
|
"ap1-custody-v3-6e0376d2"
|
|
],
|
|
"evidenceFiles": []
|
|
},
|
|
{
|
|
"title": "Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출",
|
|
"slug": "split-custody-access-token",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#검토한-선택지와-막힌-지점-ap2",
|
|
"final/document.md#선택의-이유와-지킨-경계-ap2",
|
|
"final/document.md#결정이-지켜지는지-확인하는-방법-ap2"
|
|
],
|
|
"code": [
|
|
"Spring oauth2Login mediator",
|
|
"/token/access",
|
|
"/token/boundary"
|
|
],
|
|
"classification": "one-time handoff 가 되는지 확인했고 access-only handoff 까지만 성립한다는 범위로 닫았다",
|
|
"missing-verification": "access token 전달 횟수 제한, durable store, logout, 만료 뒤 실제 refresh 는 검증하지 못했다",
|
|
"relations": [
|
|
"case:spa-browser-credential-boundary",
|
|
"case:bff-session-csrf-responsibility",
|
|
"reference:oauth-token-application-session-boundary",
|
|
"question:refresh-rotation-replica-contention"
|
|
],
|
|
"kind": "case",
|
|
"publication": "게시됨",
|
|
"file": "oauth-oidc-auth-boundary/case/case-ap2-split-custody.md",
|
|
"status": "게시 중",
|
|
"studioId": "488ce49b-afa4-42a5-a2ce-de2e0653cd82",
|
|
"assets": [
|
|
"ap2-split-custody-779cb791"
|
|
],
|
|
"evidenceFiles": []
|
|
},
|
|
{
|
|
"title": "BFF에서 Browser Token을 제거하고 Session과 CSRF를 처리한 방식",
|
|
"slug": "bff-session-csrf-responsibility",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#검토한-선택지와-막힌-지점-ap3",
|
|
"final/document.md#선택의-이유와-지킨-경계-ap3",
|
|
"final/document.md#결정이-지켜지는지-확인하는-방법-ap3"
|
|
],
|
|
"code": [
|
|
"bff-confidential client",
|
|
"XSRF-TOKEN",
|
|
"/bff/token-boundary"
|
|
],
|
|
"classification": "브라우저에서 토큰이 사라진 대신 서버가 무엇을 떠안았는지를 세 요청으로 재현하고 닫았다",
|
|
"missing-verification": "shared session store, token 암호화, logout, per-route 인가는 계약 밖이고 preference 는 process-global AtomicReference 하나다",
|
|
"relations": [
|
|
"case:split-custody-access-token",
|
|
"concept:cookie-auth-csrf",
|
|
"reference:bff-authentication-design-criteria",
|
|
"decision:bff-owns-token-when-browser-must-not",
|
|
"question:bff-session-authorized-client-store"
|
|
],
|
|
"kind": "case",
|
|
"publication": "게시됨",
|
|
"file": "oauth-oidc-auth-boundary/case/case-ap3-bff-session-csrf.md",
|
|
"status": "게시 중",
|
|
"studioId": "d85bd6af-7599-4ef7-9407-6609927d5b5c",
|
|
"assets": [
|
|
"ap3-bff-custody-82fa18bd",
|
|
"ap3-csrf-split-501dd1f7"
|
|
],
|
|
"evidenceFiles": []
|
|
},
|
|
{
|
|
"title": "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유",
|
|
"slug": "identity-header-trust",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#검토한-선택지와-막힌-지점-ap4",
|
|
"final/document.md#선택의-이유와-지킨-경계-ap4",
|
|
"final/document.md#결정이-지켜지는지-확인하는-방법-ap4"
|
|
],
|
|
"code": [
|
|
"oauth2-proxy 7.15.2",
|
|
"Nginx auth_request",
|
|
"/edge/me"
|
|
],
|
|
"classification": "위조 header 를 실제로 보내 Nginx 가 덮어쓰는지 관측했고 세 겹 방어가 성립하는 조건까지 닫았다",
|
|
"missing-verification": "role propagation, 신규 endpoint 의 공통 강제, state-changing 요청의 CSRF, session renewal, replica 공유, secret rotation 이 남아 있다",
|
|
"relations": [
|
|
"case:bff-session-csrf-responsibility",
|
|
"concept:forward-auth-and-auth-request",
|
|
"reference:forward-auth-identity-header-trust",
|
|
"question:edge-authorization-scope"
|
|
],
|
|
"kind": "case",
|
|
"publication": "게시됨",
|
|
"file": "oauth-oidc-auth-boundary/case/case-ap4-identity-header-trust.md",
|
|
"status": "게시 중",
|
|
"studioId": "a0e1cc05-92b3-4dac-bce1-513ab8cd862b",
|
|
"assets": [
|
|
"ap4-edge-trust-1cff2399"
|
|
],
|
|
"evidenceFiles": []
|
|
}
|
|
],
|
|
"concept": [
|
|
{
|
|
"title": "Authorization Code와 PKCE가 보호하는 구간",
|
|
"slug": "authorization-code-and-pkce",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#문제를-어렵게-만든-제약-로그인-흐름과-api-흐름"
|
|
],
|
|
"basis-version": "Keycloak 26.7.0 · oidc-client-ts",
|
|
"classification": "code 교환 구간이 로그인 구간과 API 구간으로 갈린다는 것을 먼저 알아야 네 Case 의 경계를 읽을 수 있다",
|
|
"relations": [
|
|
"case:spa-browser-credential-boundary",
|
|
"reference:authorization-code-endpoint-credential-movement"
|
|
],
|
|
"kind": "concept",
|
|
"publication": "게시됨",
|
|
"file": "oauth-oidc-auth-boundary/concept/concept-authorization-code-and-pkce.md",
|
|
"status": "게시 전",
|
|
"studioId": "75c6c657-3e03-47a0-a9d0-5637fce9dd3f",
|
|
"assets": [],
|
|
"evidenceFiles": []
|
|
},
|
|
{
|
|
"title": "브라우저가 credential을 보관하는 위치와 그 성질",
|
|
"slug": "browser-credential-storage",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#문제를-어렵게-만든-제약-브라우저에-없다"
|
|
],
|
|
"basis-version": "Keycloak 26.7.0 · oidc-client-ts · oauth2-proxy 7.15.2",
|
|
"classification": "「브라우저에 없다」가 무엇이 없다는 뜻인지 구분해야 AP1 과 AP3·AP4 의 차이를 오해하지 않는다",
|
|
"relations": [
|
|
"case:spa-browser-credential-boundary",
|
|
"reference:oauth-token-application-session-boundary"
|
|
],
|
|
"kind": "concept",
|
|
"publication": "게시됨",
|
|
"file": "oauth-oidc-auth-boundary/concept/concept-browser-credential-storage.md",
|
|
"status": "게시 전",
|
|
"studioId": "bb5c37ae-2d94-48f7-ad4e-a37c61c3fd07",
|
|
"assets": [],
|
|
"evidenceFiles": []
|
|
},
|
|
{
|
|
"title": "Bearer JWT가 인증된 principal이 되기까지",
|
|
"slug": "bearer-jwt-validation-chain",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#선택의-이유와-지킨-경계-ap1"
|
|
],
|
|
"basis-version": "Keycloak 26.7.0 · Spring Security OAuth2 Resource Server",
|
|
"classification": "보호 자원이 서명만 보지 않고 issuer·audience·시간까지 본다는 것을 알아야 401 판정을 읽을 수 있다",
|
|
"relations": [
|
|
"case:spa-browser-credential-boundary",
|
|
"case:split-custody-access-token"
|
|
],
|
|
"kind": "concept",
|
|
"publication": "게시됨",
|
|
"file": "oauth-oidc-auth-boundary/concept/concept-bearer-jwt-validation-chain.md",
|
|
"status": "게시 전",
|
|
"studioId": "87000d59-b69f-4010-9481-0b71c8bde32d",
|
|
"assets": [],
|
|
"evidenceFiles": []
|
|
},
|
|
{
|
|
"title": "Cookie로 인증하는 요청에서 CSRF token이 하는 일",
|
|
"slug": "cookie-auth-csrf",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#선택의-이유와-지킨-경계-ap3"
|
|
],
|
|
"basis-version": "Spring Security 6 CSRF · AP3 BFF 구성",
|
|
"classification": "cookie 가 자동 첨부된다는 성질을 알아야 BFF 가 CSRF 를 떠안는 이유가 설명된다",
|
|
"relations": [
|
|
"case:bff-session-csrf-responsibility",
|
|
"reference:bff-authentication-design-criteria"
|
|
],
|
|
"kind": "concept",
|
|
"publication": "게시됨",
|
|
"file": "oauth-oidc-auth-boundary/concept/concept-cookie-auth-csrf.md",
|
|
"status": "게시 전",
|
|
"studioId": "5c8f12d5-1ead-469b-8e91-2de69401df48",
|
|
"assets": [],
|
|
"evidenceFiles": []
|
|
},
|
|
{
|
|
"title": "Forward-Auth와 Nginx auth_request의 동작",
|
|
"slug": "forward-auth-and-auth-request",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#선택의-이유와-지킨-경계-ap4"
|
|
],
|
|
"basis-version": "oauth2-proxy 7.15.2 · Nginx auth_request module",
|
|
"classification": "실제 요청 전에 별도 endpoint 에 허용을 묻는 구조를 알아야 header 신뢰 조건이 왜 인프라 문제인지 보인다",
|
|
"relations": [
|
|
"case:identity-header-trust",
|
|
"reference:forward-auth-identity-header-trust"
|
|
],
|
|
"kind": "concept",
|
|
"publication": "게시됨",
|
|
"file": "oauth-oidc-auth-boundary/concept/concept-forward-auth-and-auth-request.md",
|
|
"status": "게시 전",
|
|
"studioId": "a3493786-d3fb-4b01-b1c5-ecb23c3d5497",
|
|
"assets": [],
|
|
"evidenceFiles": []
|
|
},
|
|
{
|
|
"title": "외부 IdP Brokering의 동작",
|
|
"slug": "idp-brokering",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-google-login"
|
|
],
|
|
"basis-version": "Keycloak 26.7.0 identity brokering",
|
|
"classification": "Google 이 Keycloak 앞의 upstream 이라는 구조를 알아야 그것이 다섯 번째 패턴이 아닌 이유가 성립한다",
|
|
"relations": [
|
|
"decision:federation-is-not-an-application-pattern",
|
|
"reference:external-idp-federation-application-boundary"
|
|
],
|
|
"kind": "concept",
|
|
"publication": "게시됨",
|
|
"file": "oauth-oidc-auth-boundary/concept/concept-idp-brokering.md",
|
|
"status": "게시 전",
|
|
"studioId": "d99fdec9-fe9e-4e0f-a50b-6fb9b9ed5719",
|
|
"assets": [],
|
|
"evidenceFiles": []
|
|
}
|
|
],
|
|
"reference": [
|
|
{
|
|
"title": "OAuth/OIDC 인증 패턴 선택 기준",
|
|
"slug": "oauth-oidc-pattern-selection-criteria",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#검토한-선택지와-막힌-지점-책임과-데이터",
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-사다리가-아니라",
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-변경-경로"
|
|
],
|
|
"classification": "다섯 항목으로 구조를 비교하고 피해야 할 조건을 먼저 보는 절차라 다음 프로젝트에도 그대로 적용된다",
|
|
"scope": "OAuth/OIDC 로 브라우저 인증을 붙이는 구조를 고르는 자리",
|
|
"exceptions": "브라우저가 없는 machine-to-machine 경로는 credential 보관 위치가 문제되지 않아 이 다섯 축이 걸리지 않는다",
|
|
"relations": [
|
|
"case:spa-browser-credential-boundary",
|
|
"case:identity-header-trust",
|
|
"reference:oauth-token-application-session-boundary"
|
|
],
|
|
"kind": "reference",
|
|
"publication": "게시됨",
|
|
"file": "oauth-oidc-auth-boundary/reference/reference-pattern-selection.md",
|
|
"status": "게시 중",
|
|
"studioId": "3f886154-1b85-407b-bda4-57d28370e745",
|
|
"assets": [],
|
|
"evidenceFiles": []
|
|
},
|
|
{
|
|
"title": "Authorization Code Flow의 Endpoint와 Credential 이동 기준",
|
|
"slug": "authorization-code-endpoint-credential-movement",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#문제를-어렵게-만든-제약-로그인-흐름과-api-흐름",
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-추적-규칙"
|
|
],
|
|
"classification": "요청 하나를 입력·변환·다음 홉·출력 네 칸으로 기록하는 방법이라 다른 인증 구조를 읽을 때도 쓴다",
|
|
"scope": "code 교환이 있는 로그인 흐름을 처음 읽는 자리",
|
|
"exceptions": "code 를 쓰지 않는 client credentials 나 이미 발급된 token 만 다루는 경로에는 이 이동표가 필요 없다",
|
|
"relations": [
|
|
"concept:authorization-code-and-pkce",
|
|
"reference:public-confidential-client-boundary"
|
|
],
|
|
"kind": "reference",
|
|
"publication": "게시됨",
|
|
"file": "oauth-oidc-auth-boundary/reference/reference-authorization-code-endpoints.md",
|
|
"status": "게시 중",
|
|
"studioId": "39fdf472-82c4-43ed-abec-73de672f08ae",
|
|
"assets": [],
|
|
"evidenceFiles": []
|
|
},
|
|
{
|
|
"title": "Public Client와 Confidential Client 구분 기준",
|
|
"slug": "public-confidential-client-boundary",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#검토한-선택지와-막힌-지점-책임과-데이터",
|
|
"final/document.md#선택의-이유와-지킨-경계-ap1",
|
|
"final/document.md#선택의-이유와-지킨-경계-ap2"
|
|
],
|
|
"classification": "secret 을 숨길 수 있는지로 client 종류가 갈리고 그것이 PKCE 요구와 이어진다",
|
|
"scope": "OAuth client 를 새로 등록하는 자리",
|
|
"exceptions": "server 끼리만 부르는 client 는 브라우저 노출 축이 없어 이 구분의 비용 계산이 달라진다",
|
|
"relations": [
|
|
"concept:authorization-code-and-pkce",
|
|
"case:spa-browser-credential-boundary"
|
|
],
|
|
"kind": "reference",
|
|
"publication": "게시됨",
|
|
"file": "oauth-oidc-auth-boundary/reference/reference-public-confidential-client.md",
|
|
"status": "게시 중",
|
|
"studioId": "ede6b9ce-eeed-40c8-9175-9e8116029395",
|
|
"assets": [],
|
|
"evidenceFiles": []
|
|
},
|
|
{
|
|
"title": "OAuth Token과 Application Session을 구분하는 기준",
|
|
"slug": "oauth-token-application-session-boundary",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#문제를-어렵게-만든-제약-같은-사용자를-나타내도",
|
|
"final/document.md#문제를-어렵게-만든-제약-브라우저에-없다"
|
|
],
|
|
"classification": "같은 사용자 이름이라도 JWT claim 과 edge header 는 검증 방식이 달라 하나로 묶으면 안 된다",
|
|
"scope": "인증 상태를 저장하거나 전달하는 모든 경계",
|
|
"exceptions": "IdP 의 SSO 상태는 애플리케이션 credential 이 아니므로 이 구분의 대상이 아니다",
|
|
"relations": [
|
|
"concept:browser-credential-storage",
|
|
"case:split-custody-access-token"
|
|
],
|
|
"kind": "reference",
|
|
"publication": "게시됨",
|
|
"file": "oauth-oidc-auth-boundary/reference/reference-token-vs-session.md",
|
|
"status": "게시 중",
|
|
"studioId": "66c18e42-116c-459f-86bd-b7e4bf394866",
|
|
"assets": [],
|
|
"evidenceFiles": []
|
|
},
|
|
{
|
|
"title": "BFF 인증 구조 설계 기준",
|
|
"slug": "bff-authentication-design-criteria",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#검토한-선택지와-막힌-지점-ap3",
|
|
"final/document.md#선택의-이유와-지킨-경계-ap3",
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-ap3"
|
|
],
|
|
"classification": "브라우저에서 토큰을 없앨 때 서버가 새로 떠안는 항목의 목록이라 다음 BFF 에도 적용된다",
|
|
"scope": "브라우저에 OAuth token 을 두지 않기로 한 구조",
|
|
"exceptions": "stateless direct API 와 독립 client 가 핵심 요구면 BFF 를 두지 않는 편이 맞다",
|
|
"relations": [
|
|
"case:bff-session-csrf-responsibility",
|
|
"concept:cookie-auth-csrf",
|
|
"decision:bff-owns-token-when-browser-must-not"
|
|
],
|
|
"kind": "reference",
|
|
"publication": "게시됨",
|
|
"file": "oauth-oidc-auth-boundary/reference/reference-bff-auth-design.md",
|
|
"status": "게시 중",
|
|
"studioId": "97eddd97-1096-426a-a2c6-a6c5bf1cd09f",
|
|
"assets": [],
|
|
"evidenceFiles": []
|
|
},
|
|
{
|
|
"title": "Forward-Auth에서 Identity Header를 신뢰하기 위한 조건",
|
|
"slug": "forward-auth-identity-header-trust",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#검토한-선택지와-막힌-지점-ap4",
|
|
"final/document.md#선택의-이유와-지킨-경계-ap4",
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-ap4"
|
|
],
|
|
"classification": "header 를 인증 근거로 쓰려면 네트워크·덮어쓰기·내부 자격 증명이 서로 독립된 방어선이어야 한다",
|
|
"scope": "edge 가 인증을 대신하고 upstream 이 header 를 믿는 구조",
|
|
"exceptions": "backend 로 직접 들어오는 경로를 막을 수 없으면 이 구조 자체를 고르지 않는다",
|
|
"relations": [
|
|
"case:identity-header-trust",
|
|
"concept:forward-auth-and-auth-request"
|
|
],
|
|
"kind": "reference",
|
|
"publication": "게시됨",
|
|
"file": "oauth-oidc-auth-boundary/reference/reference-forward-auth-header-trust.md",
|
|
"status": "게시 중",
|
|
"studioId": "004dd0a2-5fb3-4f25-80c9-576f709de331",
|
|
"assets": [],
|
|
"evidenceFiles": []
|
|
},
|
|
{
|
|
"title": "외부 IdP 연동과 Application 인증 구조의 경계",
|
|
"slug": "external-idp-federation-application-boundary",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-google-login"
|
|
],
|
|
"classification": "upstream IdP 검증 범위와 application credential 경계를 분리하는 기준이라 다른 federation 에도 적용된다",
|
|
"scope": "Keycloak 같은 broker 앞에 외부 IdP 를 두는 구성",
|
|
"exceptions": "애플리케이션이 외부 IdP 와 직접 OIDC 를 맺으면 broker 경계가 없어 이 분리가 성립하지 않는다",
|
|
"relations": [
|
|
"concept:idp-brokering",
|
|
"decision:federation-is-not-an-application-pattern"
|
|
],
|
|
"kind": "reference",
|
|
"publication": "게시됨",
|
|
"file": "oauth-oidc-auth-boundary/reference/reference-idp-federation-boundary.md",
|
|
"status": "게시 중",
|
|
"studioId": "1a00a640-8987-4075-a9e4-7ec023cdffbb",
|
|
"assets": [],
|
|
"evidenceFiles": []
|
|
},
|
|
{
|
|
"title": "패턴 검증을 실제로 돌릴 때의 안전한 순서",
|
|
"slug": "runtime-verification-safe-order",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#결정이-지켜지는지-확인하는-방법-실제-runtime-검증을-수행할-때의-안전한-순서"
|
|
],
|
|
"classification": "사전 조건·순서·중단 조건이 갖춰진 운영 절차이고 다른 프로젝트의 파괴적 검증에도 그대로 적용된다",
|
|
"scope": "volume 을 지우고 스택을 다시 세우는 검증 절차를 돌리는 자리",
|
|
"exceptions": "일회용 환경이 아닌 곳에서는 이 절차를 그대로 실행하지 않고 별도 project 로 복제한 뒤 돌린다",
|
|
"relations": [
|
|
"case:identity-header-trust",
|
|
"reference:oauth-oidc-pattern-selection-criteria"
|
|
],
|
|
"kind": "reference",
|
|
"publication": "초안",
|
|
"file": "oauth-oidc-auth-boundary/reference/reference-runtime-verification-order.md",
|
|
"status": "게시 전",
|
|
"studioId": "",
|
|
"assets": [],
|
|
"evidenceFiles": []
|
|
}
|
|
],
|
|
"question": [
|
|
{
|
|
"title": "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가",
|
|
"slug": "bff-session-authorized-client-store",
|
|
"readiness": "OPEN",
|
|
"source": [
|
|
"final/document.md#검토한-선택지와-막힌-지점-ap3",
|
|
"final/document.md#문제를-어렵게-만든-제약-학습-환경",
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-ap3"
|
|
],
|
|
"known": "현재 구현은 단일 인스턴스 memory 만 사용한다. 재시작이나 replica 이동 뒤 로그인 유지는 확인하지 못했다",
|
|
"unknown": "세션과 authorized client 를 어느 저장소에 둘 것인가, 저장 token 암호화와 key 교체를 어떻게 할 것인가",
|
|
"next-verification": "shared store 를 붙인 뒤 재시작과 replica 이동에서 로그인이 유지되는지 확인한다",
|
|
"decision-criterion": "재시작과 replica 이동 양쪽에서 로그인이 유지되면 그 저장소를 채택하는 Decision 으로 넘긴다",
|
|
"relations": [
|
|
"case:bff-session-csrf-responsibility",
|
|
"reference:bff-authentication-design-criteria",
|
|
"decision:bff-owns-token-when-browser-must-not"
|
|
],
|
|
"kind": "question",
|
|
"publication": "게시됨",
|
|
"file": "oauth-oidc-auth-boundary/question/question-bff-state-store.md",
|
|
"status": "게시 중",
|
|
"studioId": "18a5cde2-dd1e-4bff-9f1c-997577ae438f",
|
|
"assets": [],
|
|
"evidenceFiles": []
|
|
},
|
|
{
|
|
"title": "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가",
|
|
"slug": "server-session-pattern-multi-instance",
|
|
"readiness": "OPEN",
|
|
"source": [
|
|
"final/document.md#문제를-어렵게-만든-제약-학습-환경",
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-ap2"
|
|
],
|
|
"known": "AP2 와 AP3 가 session 과 authorized client 를 여러 인스턴스에서 공유하는지 확인하지 못했다",
|
|
"unknown": "session affinity 로 갈 것인가 shared store 로 갈 것인가, 장애 복구 시간은 얼마인가",
|
|
"next-verification": "두 인스턴스를 띄우고 요청을 번갈아 보내며 로그인 상태가 유지되는지 본다",
|
|
"decision-criterion": "한쪽 방식이 재인증 없이 유지되면 그것을 채택하고, 둘 다 실패하면 세션 모델 자체를 다시 정한다",
|
|
"relations": [
|
|
"case:split-custody-access-token",
|
|
"question:bff-session-authorized-client-store"
|
|
],
|
|
"kind": "question",
|
|
"publication": "게시됨",
|
|
"file": "oauth-oidc-auth-boundary/question/question-multi-instance-session.md",
|
|
"status": "게시 중",
|
|
"studioId": "c72656b5-842d-45d9-b5f6-82b66b09d0b9",
|
|
"assets": [],
|
|
"evidenceFiles": []
|
|
},
|
|
{
|
|
"title": "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가",
|
|
"slug": "refresh-rotation-replica-contention",
|
|
"readiness": "OPEN",
|
|
"source": [
|
|
"final/document.md#선택의-이유와-지킨-경계-ap1",
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-ap2"
|
|
],
|
|
"known": "AP1 은 rotation 과 reuse 0 을 쓴다. 이미 발급된 access token 은 만료 전까지 유효하다",
|
|
"unknown": "여러 replica 가 같은 refresh token 을 동시에 쓰면 무엇이 먼저 거부되는가, 재인증을 어떻게 이어 갈 것인가",
|
|
"next-verification": "두 replica 에서 동시에 refresh 를 시도해 어느 쪽이 거부되고 사용자에게 무엇이 보이는지 관측한다",
|
|
"decision-criterion": "한쪽만 거부되고 재인증으로 복구되면 닫고, 사용자 세션이 끊기면 rotation 정책을 다시 정하는 Decision 으로 넘긴다",
|
|
"relations": [
|
|
"case:split-custody-access-token",
|
|
"question:server-session-pattern-multi-instance"
|
|
],
|
|
"kind": "question",
|
|
"publication": "게시됨",
|
|
"file": "oauth-oidc-auth-boundary/question/question-refresh-rotation-replica.md",
|
|
"status": "게시 중",
|
|
"studioId": "9ae4ec71-a32e-49a7-88c2-f7368541c28d",
|
|
"assets": [],
|
|
"evidenceFiles": []
|
|
},
|
|
{
|
|
"title": "Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가",
|
|
"slug": "edge-authorization-scope",
|
|
"readiness": "OPEN",
|
|
"source": [
|
|
"final/document.md#검토한-선택지와-막힌-지점-ap4",
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-ap4"
|
|
],
|
|
"known": "현재는 user 와 email 만 전달하고 internal token 확인은 controller 하나에만 있다. /edge/** 전체를 filter 가 보호하지는 않는다",
|
|
"unknown": "role 과 claim 을 어디까지 header 로 투영할 것인가, 공통 강제를 filter 로 옮길 것인가 policy service 로 뺄 것인가",
|
|
"next-verification": "endpoint 를 하나 더 추가해 같은 검사가 자동으로 걸리는지 확인한다",
|
|
"decision-criterion": "새 endpoint 가 검사 없이 통과하면 공통 filter 로 옮기는 Decision 으로 넘긴다",
|
|
"relations": [
|
|
"case:identity-header-trust",
|
|
"reference:forward-auth-identity-header-trust"
|
|
],
|
|
"kind": "question",
|
|
"publication": "게시됨",
|
|
"file": "oauth-oidc-auth-boundary/question/question-edge-authorization-scope.md",
|
|
"status": "게시 중",
|
|
"studioId": "7ff40767-a00b-4db2-98f6-0cdfce8c8936",
|
|
"assets": [],
|
|
"evidenceFiles": []
|
|
}
|
|
],
|
|
"decision": [
|
|
{
|
|
"title": "BFF가 OAuth Token을 관리하는 조건",
|
|
"slug": "bff-owns-token-when-browser-must-not",
|
|
"readiness": "READY",
|
|
"decision-status": "PROPOSED",
|
|
"source": [
|
|
"final/document.md#선택의-이유와-지킨-경계-ap3",
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-ap3"
|
|
],
|
|
"decision-evidence": [
|
|
"case:bff-session-csrf-responsibility",
|
|
"reference:bff-authentication-design-criteria"
|
|
],
|
|
"grounds": "브라우저에서 OAuth token 을 없애려면 서버가 code 교환뿐 아니라 API 호출까지 맡아야 하고, 그 순간 session·CSRF·저장소가 함께 따라온다",
|
|
"classification": "AP1·AP2 를 대안으로 두고 브라우저 토큰 금지가 요구일 때만 BFF 를 고르기로 정했다",
|
|
"relations": [
|
|
"case:bff-session-csrf-responsibility",
|
|
"question:bff-session-authorized-client-store"
|
|
],
|
|
"kind": "decision",
|
|
"publication": "게시됨",
|
|
"file": "oauth-oidc-auth-boundary/decision/decision-bff-owns-token.md",
|
|
"status": "게시 중",
|
|
"studioId": "19b55c39-c583-4161-9775-df954280a568",
|
|
"assets": [],
|
|
"evidenceFiles": []
|
|
},
|
|
{
|
|
"title": "외부 IdP와의 연동이라도 별도의 인증 방식이 아니다.",
|
|
"slug": "federation-is-not-an-application-pattern",
|
|
"readiness": "READY",
|
|
"decision-status": "ADOPTED",
|
|
"source": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-google-login"
|
|
],
|
|
"decision-evidence": [
|
|
"concept:idp-brokering",
|
|
"reference:external-idp-federation-application-boundary"
|
|
],
|
|
"grounds": "Google 은 Keycloak 앞의 upstream 이고 애플리케이션으로 나가는 데이터는 다시 Keycloak 이 만든다. 네 패턴의 downstream 경계는 그대로다",
|
|
"classification": "federation 을 다섯 번째 패턴으로 세는 대안을 두고 두 protocol boundary 를 섞지 않기로 정했다",
|
|
"relations": [
|
|
"concept:idp-brokering",
|
|
"reference:external-idp-federation-application-boundary"
|
|
],
|
|
"kind": "decision",
|
|
"publication": "게시됨",
|
|
"file": "oauth-oidc-auth-boundary/decision/decision-federation-not-a-pattern.md",
|
|
"status": "게시 중",
|
|
"studioId": "8c1ebea7-204e-445c-9812-0421d9eb0e9c",
|
|
"assets": [],
|
|
"evidenceFiles": []
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"candidates": [
|
|
{
|
|
"id": "SSOT-spa-browser-credential-boundary",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#검토한-선택지와-막힌-지점-ap1",
|
|
"final/document.md#선택의-이유와-지킨-경계-ap1",
|
|
"final/document.md#결정이-지켜지는지-확인하는-방법-ap1"
|
|
],
|
|
"summary": "SPA에서 OAuth Token을 JavaScript Memory에 보관한 경우",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:spa-browser-credential-boundary",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-split-custody-access-token",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#검토한-선택지와-막힌-지점-ap2",
|
|
"final/document.md#선택의-이유와-지킨-경계-ap2",
|
|
"final/document.md#결정이-지켜지는지-확인하는-방법-ap2"
|
|
],
|
|
"summary": "Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:split-custody-access-token",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-bff-session-csrf-responsibility",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#검토한-선택지와-막힌-지점-ap3",
|
|
"final/document.md#선택의-이유와-지킨-경계-ap3",
|
|
"final/document.md#결정이-지켜지는지-확인하는-방법-ap3"
|
|
],
|
|
"summary": "BFF에서 Browser Token을 제거하고 Session과 CSRF를 처리한 방식",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:bff-session-csrf-responsibility",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-identity-header-trust",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#검토한-선택지와-막힌-지점-ap4",
|
|
"final/document.md#선택의-이유와-지킨-경계-ap4",
|
|
"final/document.md#결정이-지켜지는지-확인하는-방법-ap4"
|
|
],
|
|
"summary": "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:identity-header-trust",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-authorization-code-and-pkce",
|
|
"kindCandidate": "CONCEPT",
|
|
"sourceRefs": [
|
|
"final/document.md#문제를-어렵게-만든-제약-로그인-흐름과-api-흐름"
|
|
],
|
|
"summary": "Authorization Code와 PKCE가 보호하는 구간",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "concept:authorization-code-and-pkce",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-browser-credential-storage",
|
|
"kindCandidate": "CONCEPT",
|
|
"sourceRefs": [
|
|
"final/document.md#문제를-어렵게-만든-제약-브라우저에-없다"
|
|
],
|
|
"summary": "브라우저가 credential을 보관하는 위치와 그 성질",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "concept:browser-credential-storage",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-bearer-jwt-validation-chain",
|
|
"kindCandidate": "CONCEPT",
|
|
"sourceRefs": [
|
|
"final/document.md#선택의-이유와-지킨-경계-ap1"
|
|
],
|
|
"summary": "Bearer JWT가 인증된 principal이 되기까지",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "concept:bearer-jwt-validation-chain",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-cookie-auth-csrf",
|
|
"kindCandidate": "CONCEPT",
|
|
"sourceRefs": [
|
|
"final/document.md#선택의-이유와-지킨-경계-ap3"
|
|
],
|
|
"summary": "Cookie로 인증하는 요청에서 CSRF token이 하는 일",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "concept:cookie-auth-csrf",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-forward-auth-and-auth-request",
|
|
"kindCandidate": "CONCEPT",
|
|
"sourceRefs": [
|
|
"final/document.md#선택의-이유와-지킨-경계-ap4"
|
|
],
|
|
"summary": "Forward-Auth와 Nginx auth_request의 동작",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "concept:forward-auth-and-auth-request",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-idp-brokering",
|
|
"kindCandidate": "CONCEPT",
|
|
"sourceRefs": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-google-login"
|
|
],
|
|
"summary": "외부 IdP Brokering의 동작",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "concept:idp-brokering",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-oauth-oidc-pattern-selection-criteria",
|
|
"kindCandidate": "REFERENCE",
|
|
"sourceRefs": [
|
|
"final/document.md#검토한-선택지와-막힌-지점-책임과-데이터",
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-사다리가-아니라",
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-변경-경로"
|
|
],
|
|
"summary": "OAuth/OIDC 인증 패턴 선택 기준",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "reference:oauth-oidc-pattern-selection-criteria",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-authorization-code-endpoint-credential-movement",
|
|
"kindCandidate": "REFERENCE",
|
|
"sourceRefs": [
|
|
"final/document.md#문제를-어렵게-만든-제약-로그인-흐름과-api-흐름",
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-추적-규칙"
|
|
],
|
|
"summary": "Authorization Code Flow의 Endpoint와 Credential 이동 기준",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "reference:authorization-code-endpoint-credential-movement",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-public-confidential-client-boundary",
|
|
"kindCandidate": "REFERENCE",
|
|
"sourceRefs": [
|
|
"final/document.md#검토한-선택지와-막힌-지점-책임과-데이터",
|
|
"final/document.md#선택의-이유와-지킨-경계-ap1",
|
|
"final/document.md#선택의-이유와-지킨-경계-ap2"
|
|
],
|
|
"summary": "Public Client와 Confidential Client 구분 기준",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "reference:public-confidential-client-boundary",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-oauth-token-application-session-boundary",
|
|
"kindCandidate": "REFERENCE",
|
|
"sourceRefs": [
|
|
"final/document.md#문제를-어렵게-만든-제약-같은-사용자를-나타내도",
|
|
"final/document.md#문제를-어렵게-만든-제약-브라우저에-없다"
|
|
],
|
|
"summary": "OAuth Token과 Application Session을 구분하는 기준",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "reference:oauth-token-application-session-boundary",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-bff-authentication-design-criteria",
|
|
"kindCandidate": "REFERENCE",
|
|
"sourceRefs": [
|
|
"final/document.md#검토한-선택지와-막힌-지점-ap3",
|
|
"final/document.md#선택의-이유와-지킨-경계-ap3",
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-ap3"
|
|
],
|
|
"summary": "BFF 인증 구조 설계 기준",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "reference:bff-authentication-design-criteria",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-forward-auth-identity-header-trust",
|
|
"kindCandidate": "REFERENCE",
|
|
"sourceRefs": [
|
|
"final/document.md#검토한-선택지와-막힌-지점-ap4",
|
|
"final/document.md#선택의-이유와-지킨-경계-ap4",
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-ap4"
|
|
],
|
|
"summary": "Forward-Auth에서 Identity Header를 신뢰하기 위한 조건",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "reference:forward-auth-identity-header-trust",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-external-idp-federation-application-boundary",
|
|
"kindCandidate": "REFERENCE",
|
|
"sourceRefs": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-google-login"
|
|
],
|
|
"summary": "외부 IdP 연동과 Application 인증 구조의 경계",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "reference:external-idp-federation-application-boundary",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-runtime-verification-safe-order",
|
|
"kindCandidate": "REFERENCE",
|
|
"sourceRefs": [
|
|
"final/document.md#결정이-지켜지는지-확인하는-방법-실제-runtime-검증을-수행할-때의-안전한-순서"
|
|
],
|
|
"summary": "패턴 검증을 실제로 돌릴 때의 안전한 순서",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "reference:runtime-verification-safe-order",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-bff-session-authorized-client-store",
|
|
"kindCandidate": "QUESTION",
|
|
"sourceRefs": [
|
|
"final/document.md#검토한-선택지와-막힌-지점-ap3",
|
|
"final/document.md#문제를-어렵게-만든-제약-학습-환경",
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-ap3"
|
|
],
|
|
"summary": "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "question:bff-session-authorized-client-store",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-server-session-pattern-multi-instance",
|
|
"kindCandidate": "QUESTION",
|
|
"sourceRefs": [
|
|
"final/document.md#문제를-어렵게-만든-제약-학습-환경",
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-ap2"
|
|
],
|
|
"summary": "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "question:server-session-pattern-multi-instance",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-refresh-rotation-replica-contention",
|
|
"kindCandidate": "QUESTION",
|
|
"sourceRefs": [
|
|
"final/document.md#선택의-이유와-지킨-경계-ap1",
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-ap2"
|
|
],
|
|
"summary": "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "question:refresh-rotation-replica-contention",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-edge-authorization-scope",
|
|
"kindCandidate": "QUESTION",
|
|
"sourceRefs": [
|
|
"final/document.md#검토한-선택지와-막힌-지점-ap4",
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-ap4"
|
|
],
|
|
"summary": "Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "question:edge-authorization-scope",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-bff-owns-token-when-browser-must-not",
|
|
"kindCandidate": "DECISION",
|
|
"sourceRefs": [
|
|
"final/document.md#선택의-이유와-지킨-경계-ap3",
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-ap3"
|
|
],
|
|
"summary": "BFF가 OAuth Token을 관리하는 조건",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "decision:bff-owns-token-when-browser-must-not",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-federation-is-not-an-application-pattern",
|
|
"kindCandidate": "DECISION",
|
|
"sourceRefs": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-google-login"
|
|
],
|
|
"summary": "외부 IdP와의 연동이라도 별도의 인증 방식이 아니다.",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "decision:federation-is-not-an-application-pattern",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-learning-environment-scope",
|
|
"kindCandidate": "CONCEPT",
|
|
"sourceRefs": [
|
|
"final/document.md#문제를-어렵게-만든-제약-학습-환경"
|
|
],
|
|
"summary": "현재 구현은 운영 참조 아키텍처가 아니라 관찰 가능한 학습 환경이다",
|
|
"disposition": "KEEP_IN_SSOT",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": null,
|
|
"reason": "이 분석 전체의 범위 고지다. 각 Case 의 확인하지 못한 것과 네 Question 이 그 한계를 이미 나눠 담는다"
|
|
},
|
|
{
|
|
"id": "SSOT-request-trace-four-columns",
|
|
"kindCandidate": "REFERENCE",
|
|
"sourceRefs": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-추적-규칙"
|
|
],
|
|
"summary": "요청 한 번을 입력·변환·다음 홉·출력 네 칸으로 기록한다",
|
|
"disposition": "MERGE_INTO",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "reference:authorization-code-endpoint-credential-movement",
|
|
"reason": "그 Reference 가 이미 이 네 칸으로 credential 이동을 적는다. 별도 규칙으로 빼면 같은 말이 둘이 된다"
|
|
},
|
|
{
|
|
"id": "SSOT-acceptance-contract-table",
|
|
"kindCandidate": "REFERENCE",
|
|
"sourceRefs": [
|
|
"final/document.md#결정이-지켜지는지-확인하는-방법-테스트-개수보다-경계의-input과-output"
|
|
],
|
|
"summary": "패턴별 테스트가 선언하는 acceptance contract 표",
|
|
"disposition": "MERGE_INTO",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:identity-header-trust",
|
|
"reason": "패턴마다의 입력과 기대 출력은 각 Case 의 재현 조건과 확인하지 못한 것이 이미 나눠 담는다"
|
|
},
|
|
{
|
|
"id": "SSOT-four-patterns-not-a-ladder",
|
|
"kindCandidate": "REFERENCE",
|
|
"sourceRefs": [
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-사다리가-아니라"
|
|
],
|
|
"summary": "네 패턴은 사다리가 아니라 서로 다른 운영 계약이다",
|
|
"disposition": "MERGE_INTO",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "reference:oauth-oidc-pattern-selection-criteria",
|
|
"reason": "번호로 판단하지 않는다는 것이 그 Reference 의 전제이고 규칙 1·4 가 이미 그 형태로 적는다"
|
|
},
|
|
{
|
|
"id": "SSOT-credential-contract-migration",
|
|
"kindCandidate": "REFERENCE",
|
|
"sourceRefs": [
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-변경-경로"
|
|
],
|
|
"summary": "패턴을 옮기는 일도 credential contract 의 변화로 본다",
|
|
"disposition": "MERGE_INTO",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "reference:oauth-oidc-pattern-selection-criteria",
|
|
"reason": "그 Reference 의 규칙 5 「자격 증명의 위치가 바뀌면 저장·전달·검증 주체도 바뀐다」가 같은 내용이다"
|
|
}
|
|
],
|
|
"unlisted": [],
|
|
"history": {},
|
|
"counts": {
|
|
"topics": 1,
|
|
"nodes": 24,
|
|
"written": 24,
|
|
"unwritten": 0,
|
|
"unlisted": 0,
|
|
"candidates": 29
|
|
}
|
|
}
|