docs(clean-architecture-backend-template): 제1부가 채택한 것만 글감으로 남기고 다시 고른다
글감 1,001개 중 제1부(§3~§11) 앵커를 하나라도 가진 것은 112개뿐이었다. 나머지 889개는
제2부 모듈 분석 65편의 절 제목에서 나온 것이고, 그것이 재판정이 필요했던 이유다.
주제 44 → 16 (43개가 독자 질문 없이 있었다. 지금은 전부 있다)
글감 1,001 → 123 (제1부 앵커 112 + 제1부가 채택했는데 비어 있던 자리 11)
후보 965 → 1,088 · PENDING 905 → 0
error 3,042 → 0
내려온 889개는 후보 대장에 KEEP_IN_SSOT 로 남는다 — 버린 것이 아니라 분석에 남기고 독립
기록으로 만들지 않기로 한 것이다. 그 글감을 받치던 기록 파일 828개는 지웠다. 계약이 정본이고,
파일이 남아 있다는 이유로 계약에서 뺀 주제가 되살아나면 안 된다. 이력에는 그대로 있다 —
git checkout a0ca2bb -- <경로>.
제1부가 채택했는데 글감이 없던 자리 열하나를 채웠다: mongo high-water mark 가 재전달 이벤트를
삼킨 P1, admin plane 이 가드만 켜고 서비스는 켜지 않은 것과 그 짝인 결정, 실패 어휘 세 층과
SQLState 매트릭스 병합 규칙, 부하 아래에서만 새는 admission 경계, 발행 증거와 완료 판정의
분리, keyset·JSONB 결정 둘.
Concept 17개에 basis-version 을 채우고, 계약 제목과 기록 제목이 갈라져 있던 23건을 기록 쪽에
맞췄다. candidateScope 에 excludedAnchorPattern 을 적어 제2부 앵커만 가진 글감이 다시 올라올
수 없게 한다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a0ca2bb72a
commit
1f04117bbf
+5
@@ -16,6 +16,8 @@ assets:
|
||||
file: ../../../final/assets/tech-log-studio/ap3-bff-custody.svg
|
||||
- key: ap3-csrf-split-501dd1f7
|
||||
file: ../../../final/assets/tech-log-studio/ap3-csrf-split.svg
|
||||
- key: ap3-bff-session-flow
|
||||
file: ../../../final/assets/tech-log-studio/ap3-bff-session-flow.svg
|
||||
sourceRevision: keycloak-patterns-lab@2026-08
|
||||
source:
|
||||
- final/document.md#검토한-선택지와-막힌-지점-ap3
|
||||
@@ -141,6 +143,9 @@ BFF(Backend For Frontend)는 화면에 필요한 API를 브라우저 대신 호
|
||||
|
||||
### 쿠키 하나로 시작한 요청이 Bearer 요청이 된다
|
||||
|
||||
:::evidence key="ap3-bff-session-flow" alt="브라우저에서 Spring BFF, Authorized-client store, Resource Server로 이어지는 여섯 단계 흐름. AP3_SESSION을 실은 GET /bff/api/me로 시작해 BFF가 현재 principal을 authorize하고 저장소에서 server-held access token을 받는다. 그 토큰으로 GET /api/me를 Bearer로 부르고 subject·username·issuer·audience를 받아 브라우저에 JSON으로 돌려준다." caption="" zoom="true"
|
||||
:::
|
||||
|
||||
브라우저가 `/bff/api/me`를 부를 때 요청에 붙는 자격 증명은 쿠키뿐이라, `Authorization` 헤더도 없고 브라우저 코드에는 액세스 토큰을 담는 변수도 없다.
|
||||
|
||||
```http label="브라우저 입력 — cookie 하나"
|
||||
|
||||
+6
@@ -10,6 +10,9 @@ status: 게시 전
|
||||
version: 4
|
||||
basisVersion: Spring Security 6 CSRF · AP3 BFF 구성
|
||||
studio: "https://hyeonworks.com/studio/documents/5c8f12d5-1ead-469b-8e91-2de69401df48/edit"
|
||||
assets:
|
||||
- key: ap3-csrf-boundary
|
||||
file: ../../../final/assets/tech-log-studio/ap3-csrf-boundary.svg
|
||||
sourceRevision: keycloak-patterns-lab@2026-08
|
||||
source:
|
||||
- final/document.md#선택의-이유와-지킨-경계-ap3
|
||||
@@ -60,6 +63,9 @@ Cookie: AP3_SESSION=<opaque-session-id>
|
||||
}
|
||||
```
|
||||
|
||||
:::evidence key="ap3-csrf-boundary" alt="BFF의 /bff/csrf 하나에서 두 갈래가 갈리는 그림. Set-Cookie로 나가는 CSRF 쿠키에는 가리지 않은 원본 값이 들어가고 JSON 본문에는 가린 토큰과 headerName이 들어간다. 브라우저 코드는 JSON에서 headerName만 쓰고 실제 헤더 값은 쿠키의 원본 값을 쓴다. Spring CSRF filter가 raw cookie와 raw header를 대조해 일치하면 controller로 보내고 부재나 불일치면 403을 낸다." caption="" zoom="true"
|
||||
:::
|
||||
|
||||
## body의 token과 cookie의 값은 다르다
|
||||
|
||||
같은 CSRF 값이 응답 본문, 쿠키, 요청 헤더 세 곳에 서로 다른 형태로 놓인다.
|
||||
|
||||
+6
@@ -10,6 +10,9 @@ status: 게시 전
|
||||
version: 4
|
||||
basisVersion: oauth2-proxy 7.15.2 · Nginx auth_request module
|
||||
studio: "https://hyeonworks.com/studio/documents/a3493786-d3fb-4b01-b1c5-ecb23c3d5497/edit"
|
||||
assets:
|
||||
- key: ap4-edge-forward-auth-flow
|
||||
file: ../../../final/assets/tech-log-studio/ap4-edge-forward-auth-flow.svg
|
||||
sourceRevision: keycloak-patterns-lab@2026-08
|
||||
source:
|
||||
- final/document.md#선택의-이유와-지킨-경계-ap4
|
||||
@@ -34,6 +37,9 @@ forward-auth는 실제 요청을 업스트림으로 넘기기 전에 별도의
|
||||
|
||||
## 요청 하나가 두 번 평가된다
|
||||
|
||||
:::evidence key="ap4-edge-forward-auth-flow" alt="브라우저에서 Nginx edge, oauth2-proxy, Spring upstream으로 이어지는 여섯 단계 흐름. AP4_SESSION을 실은 /api/edge 요청이 들어오면 Nginx가 internal /oauth2/auth로 subrequest를 보내 authenticated user와 email을 받는다. 그 값으로 만든 trusted header와 internal token을 붙여 /edge/me를 부르고, upstream이 돌려준 trusted identity JSON이 브라우저로 나간다." caption="" zoom="true"
|
||||
:::
|
||||
|
||||
브라우저 요청이 들어와도 Nginx는 업스트림을 바로 호출하지 않는다. 업스트림은 Nginx가 요청을 최종으로 넘기는 뒤쪽 서버이고, 이 구성에서는 `app:8081`의 Spring 애플리케이션이다. 바로 넘기지 않는 것은 `location /`에 다음 지시어가 있기 때문이다.
|
||||
|
||||
```nginx label="general location의 auth_request"
|
||||
|
||||
@@ -57,6 +57,41 @@
|
||||
"BLOCKED": "원본이 불완전하거나 서로 어긋난다"
|
||||
}
|
||||
},
|
||||
"assetLedger": {
|
||||
"note": "final/assets/ 의 그림 13장 가운데 글감에 배정한 것과, 배정하지 않은 것의 이유를 적는다. verify-project-layout.py 의 「기록이 쓰지 않는 SSOT 그림」이 세는 숫자가 여기서 설명된다",
|
||||
"assigned": [
|
||||
"ap3-bff-session-flow",
|
||||
"ap3-csrf-boundary",
|
||||
"ap4-edge-forward-auth-flow"
|
||||
],
|
||||
"unassigned": [
|
||||
{
|
||||
"asset": [
|
||||
"ap1-direct-architecture",
|
||||
"ap2-mediator-architecture",
|
||||
"ap3-bff-architecture",
|
||||
"ap4-edge-trust-architecture"
|
||||
],
|
||||
"reason": "같은 구조를 그린 그림이 assets/tech-log-studio/ 에 따로 있고 기록이 그것을 쓴다. 한 기록에 같은 것을 두 번 그리지 않는다"
|
||||
},
|
||||
{
|
||||
"asset": [
|
||||
"ap1-browser-bearer-flow",
|
||||
"ap2-mediator-handoff-flow"
|
||||
],
|
||||
"reason": "마지막 단계인 /api/me 응답 4필드(subject·username·issuer·audience)를 두 기록의 본문이 말하지 않는다. 근거는 final/document.md L474 에 있으므로 본문을 먼저 보강해야 이을 수 있다 — 그림만 넣으면 설명 없는 주장이 남는다"
|
||||
},
|
||||
{
|
||||
"asset": [
|
||||
"four-pattern-request-boundaries",
|
||||
"credential-custody-map",
|
||||
"credential-contract-migration",
|
||||
"login-api-phase-split"
|
||||
],
|
||||
"reason": "네 패턴을 나란히 비교하는 그림이라 붙을 자리가 Reference 인데 Reference 에는 본문이 없다. 이 비교를 담을 Case 나 Concept 이 아직 없다"
|
||||
}
|
||||
]
|
||||
},
|
||||
"topics": {
|
||||
"oauth-oidc-auth-boundary": {
|
||||
"topic": "oauth-oidc-auth-boundary",
|
||||
@@ -93,6 +128,9 @@
|
||||
"assets": [
|
||||
"ap1-custody-v3-6e0376d2"
|
||||
],
|
||||
"assetFiles": [
|
||||
"ap1-credential-custody"
|
||||
],
|
||||
"evidenceFiles": []
|
||||
},
|
||||
{
|
||||
@@ -125,6 +163,9 @@
|
||||
"assets": [
|
||||
"ap2-split-custody-779cb791"
|
||||
],
|
||||
"assetFiles": [
|
||||
"ap2-split-custody"
|
||||
],
|
||||
"evidenceFiles": []
|
||||
},
|
||||
{
|
||||
@@ -150,6 +191,9 @@
|
||||
"decision:bff-owns-token-when-browser-must-not",
|
||||
"question:bff-session-authorized-client-store"
|
||||
],
|
||||
"ssot-assets": [
|
||||
"ap3-bff-session-flow"
|
||||
],
|
||||
"kind": "case",
|
||||
"publication": "게시됨",
|
||||
"file": "oauth-oidc-auth-boundary/case/case-ap3-bff-session-csrf.md",
|
||||
@@ -157,7 +201,13 @@
|
||||
"studioId": "d85bd6af-7599-4ef7-9407-6609927d5b5c",
|
||||
"assets": [
|
||||
"ap3-bff-custody-82fa18bd",
|
||||
"ap3-csrf-split-501dd1f7"
|
||||
"ap3-csrf-split-501dd1f7",
|
||||
"ap3-bff-session-flow"
|
||||
],
|
||||
"assetFiles": [
|
||||
"ap3-bff-custody",
|
||||
"ap3-csrf-split",
|
||||
"ap3-bff-session-flow"
|
||||
],
|
||||
"evidenceFiles": []
|
||||
},
|
||||
@@ -191,6 +241,9 @@
|
||||
"assets": [
|
||||
"ap4-edge-trust-1cff2399"
|
||||
],
|
||||
"assetFiles": [
|
||||
"ap4-edge-trust"
|
||||
],
|
||||
"evidenceFiles": []
|
||||
}
|
||||
],
|
||||
@@ -214,6 +267,7 @@
|
||||
"status": "게시 전",
|
||||
"studioId": "75c6c657-3e03-47a0-a9d0-5637fce9dd3f",
|
||||
"assets": [],
|
||||
"assetFiles": [],
|
||||
"evidenceFiles": []
|
||||
},
|
||||
{
|
||||
@@ -235,6 +289,7 @@
|
||||
"status": "게시 전",
|
||||
"studioId": "bb5c37ae-2d94-48f7-ad4e-a37c61c3fd07",
|
||||
"assets": [],
|
||||
"assetFiles": [],
|
||||
"evidenceFiles": []
|
||||
},
|
||||
{
|
||||
@@ -256,6 +311,7 @@
|
||||
"status": "게시 전",
|
||||
"studioId": "87000d59-b69f-4010-9481-0b71c8bde32d",
|
||||
"assets": [],
|
||||
"assetFiles": [],
|
||||
"evidenceFiles": []
|
||||
},
|
||||
{
|
||||
@@ -271,12 +327,20 @@
|
||||
"case:bff-session-csrf-responsibility",
|
||||
"reference:bff-authentication-design-criteria"
|
||||
],
|
||||
"ssot-assets": [
|
||||
"ap3-csrf-boundary"
|
||||
],
|
||||
"kind": "concept",
|
||||
"publication": "게시됨",
|
||||
"file": "oauth-oidc-auth-boundary/concept/concept-cookie-auth-csrf.md",
|
||||
"status": "게시 전",
|
||||
"studioId": "5c8f12d5-1ead-469b-8e91-2de69401df48",
|
||||
"assets": [],
|
||||
"assets": [
|
||||
"ap3-csrf-boundary"
|
||||
],
|
||||
"assetFiles": [
|
||||
"ap3-csrf-boundary"
|
||||
],
|
||||
"evidenceFiles": []
|
||||
},
|
||||
{
|
||||
@@ -292,12 +356,20 @@
|
||||
"case:identity-header-trust",
|
||||
"reference:forward-auth-identity-header-trust"
|
||||
],
|
||||
"ssot-assets": [
|
||||
"ap4-edge-forward-auth-flow"
|
||||
],
|
||||
"kind": "concept",
|
||||
"publication": "게시됨",
|
||||
"file": "oauth-oidc-auth-boundary/concept/concept-forward-auth-and-auth-request.md",
|
||||
"status": "게시 전",
|
||||
"studioId": "a3493786-d3fb-4b01-b1c5-ecb23c3d5497",
|
||||
"assets": [],
|
||||
"assets": [
|
||||
"ap4-edge-forward-auth-flow"
|
||||
],
|
||||
"assetFiles": [
|
||||
"ap4-edge-forward-auth-flow"
|
||||
],
|
||||
"evidenceFiles": []
|
||||
},
|
||||
{
|
||||
@@ -319,6 +391,7 @@
|
||||
"status": "게시 전",
|
||||
"studioId": "d99fdec9-fe9e-4e0f-a50b-6fb9b9ed5719",
|
||||
"assets": [],
|
||||
"assetFiles": [],
|
||||
"evidenceFiles": []
|
||||
}
|
||||
],
|
||||
@@ -346,6 +419,7 @@
|
||||
"status": "게시 중",
|
||||
"studioId": "3f886154-1b85-407b-bda4-57d28370e745",
|
||||
"assets": [],
|
||||
"assetFiles": [],
|
||||
"evidenceFiles": []
|
||||
},
|
||||
{
|
||||
@@ -369,6 +443,7 @@
|
||||
"status": "게시 중",
|
||||
"studioId": "39fdf472-82c4-43ed-abec-73de672f08ae",
|
||||
"assets": [],
|
||||
"assetFiles": [],
|
||||
"evidenceFiles": []
|
||||
},
|
||||
{
|
||||
@@ -393,6 +468,7 @@
|
||||
"status": "게시 중",
|
||||
"studioId": "ede6b9ce-eeed-40c8-9175-9e8116029395",
|
||||
"assets": [],
|
||||
"assetFiles": [],
|
||||
"evidenceFiles": []
|
||||
},
|
||||
{
|
||||
@@ -416,6 +492,7 @@
|
||||
"status": "게시 중",
|
||||
"studioId": "66c18e42-116c-459f-86bd-b7e4bf394866",
|
||||
"assets": [],
|
||||
"assetFiles": [],
|
||||
"evidenceFiles": []
|
||||
},
|
||||
{
|
||||
@@ -441,6 +518,7 @@
|
||||
"status": "게시 중",
|
||||
"studioId": "97eddd97-1096-426a-a2c6-a6c5bf1cd09f",
|
||||
"assets": [],
|
||||
"assetFiles": [],
|
||||
"evidenceFiles": []
|
||||
},
|
||||
{
|
||||
@@ -465,6 +543,7 @@
|
||||
"status": "게시 중",
|
||||
"studioId": "004dd0a2-5fb3-4f25-80c9-576f709de331",
|
||||
"assets": [],
|
||||
"assetFiles": [],
|
||||
"evidenceFiles": []
|
||||
},
|
||||
{
|
||||
@@ -487,6 +566,7 @@
|
||||
"status": "게시 중",
|
||||
"studioId": "1a00a640-8987-4075-a9e4-7ec023cdffbb",
|
||||
"assets": [],
|
||||
"assetFiles": [],
|
||||
"evidenceFiles": []
|
||||
},
|
||||
{
|
||||
@@ -509,6 +589,7 @@
|
||||
"status": "게시 전",
|
||||
"studioId": "",
|
||||
"assets": [],
|
||||
"assetFiles": [],
|
||||
"evidenceFiles": []
|
||||
}
|
||||
],
|
||||
@@ -537,6 +618,7 @@
|
||||
"status": "게시 중",
|
||||
"studioId": "18a5cde2-dd1e-4bff-9f1c-997577ae438f",
|
||||
"assets": [],
|
||||
"assetFiles": [],
|
||||
"evidenceFiles": []
|
||||
},
|
||||
{
|
||||
@@ -561,6 +643,7 @@
|
||||
"status": "게시 중",
|
||||
"studioId": "c72656b5-842d-45d9-b5f6-82b66b09d0b9",
|
||||
"assets": [],
|
||||
"assetFiles": [],
|
||||
"evidenceFiles": []
|
||||
},
|
||||
{
|
||||
@@ -585,6 +668,7 @@
|
||||
"status": "게시 중",
|
||||
"studioId": "9ae4ec71-a32e-49a7-88c2-f7368541c28d",
|
||||
"assets": [],
|
||||
"assetFiles": [],
|
||||
"evidenceFiles": []
|
||||
},
|
||||
{
|
||||
@@ -609,6 +693,7 @@
|
||||
"status": "게시 중",
|
||||
"studioId": "7ff40767-a00b-4db2-98f6-0cdfce8c8936",
|
||||
"assets": [],
|
||||
"assetFiles": [],
|
||||
"evidenceFiles": []
|
||||
}
|
||||
],
|
||||
@@ -638,6 +723,7 @@
|
||||
"status": "게시 중",
|
||||
"studioId": "19b55c39-c583-4161-9775-df954280a568",
|
||||
"assets": [],
|
||||
"assetFiles": [],
|
||||
"evidenceFiles": []
|
||||
},
|
||||
{
|
||||
@@ -664,6 +750,7 @@
|
||||
"status": "게시 중",
|
||||
"studioId": "8c1ebea7-204e-445c-9812-0421d9eb0e9c",
|
||||
"assets": [],
|
||||
"assetFiles": [],
|
||||
"evidenceFiles": []
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user