refactor: 문서 개선 중

This commit is contained in:
donghyeon-ka
2026-09-21 14:30:55 +09:00
parent c93cdea150
commit 805a18f486
1497 changed files with 525837 additions and 59152 deletions
@@ -6,17 +6,19 @@
## Long description
Q1 은 저장소를 밖으로 빼면 다중 인스턴스와 재시작은 풀리지만 덮어쓰기와 로그아웃 정리는 남는다는 답을 얻었고 원인은 기본키에 세션 id 가 없다는 것이었다. Q2 는 이긴 요청의 토큰조차 못 쓴다는 답이다. Q3 은 세션과 토큰의 조회 키가 다르므로 각각 결정해야 한다는 답이다. Q4 프록시가 헤더를 먼저 지워야 하고 IdP 의 클레임 변경은 재인증 전까지 반영되지 않는다는 답이다.
Q1은 저장소를 밖으로 빼면 다중 인스턴스와 재시작은 풀리지만 기본키에 session id가 없어 덮어쓰기와 정리 문제가 남는다는 답이다. Q2는 refresh 경쟁이 감지되면 client session이 지워져 이긴 요청의 새 token도 쓸 수 없다는 답이다. Q3는 Application Session과 OAuth2AuthorizedClient의 조회 키가 달라 각각 저장소를 결정해야 한다는 답이다. Q4는 위조 헤더가 permitAll echo까지 도착해도 JWT 보호 경로는 401이었고, 헤더 삭제 처방은 미적용이며 IdP claim 변경은 재인증 전까지 반영되지 않는다는 답이다.
## Elements and evidence
- **열린 질문 네 개** (component): 설계로는 답할 수 없었다. Evidence: L682L690.
- **저장소 선택** (process): 다중 인스턴스와 재시작은 여기서 풀린다. Evidence: L682L692.
- **스키마와 조회 키** (process): 덮어쓰기와 정리 누락은 여기가 원인이다. Evidence: L682L694.
- **세션 스냅샷** (process): IdP 에서 클레임을 바꿔도 재인증까지 옛 값이 간다. Evidence: L682L696.
- **열린 질문 네 개** (component): 설계만으로는 닫히지 않았던 Q1부터 Q4다. Evidence: L1223L1228.
- **Q1 · 저장소와 스키마** (process): ①②는 외부 저장소로 풀리고 ③④는 기본키 문제로 남는다. Evidence: L1225L1225.
- **Q2 · 회전 경쟁** (process): 경쟁이 감지되면 client session이 지워진다. Evidence: L1226L1226.
- **Q3 · 조회 키 분리** (process): Session과 AuthorizedClient는 각각 결정해야 한다. Evidence: L1227L1227.
- **Q4 · Edge 신뢰 경계** (process): 헤더 도착과 인가 우회는 다르고 claim은 재인증까지 snapshot이다. Evidence: L1228L1228.
## Relationships
- **열린 질문 네 개 → 저장소 선택:** 1차 검토. Evidence: L682L692.
- **저장소 선택 → 스키마와 조회 키:** 미해결 잔여. Evidence: L682L694.
- **스키마와 조회 키 → 세션 스냅샷:** 최종 잔여. Evidence: L682L696.
- **열린 질문 네 개 → Q1 · 저장소와 스키마:** 답. Evidence: L1225L1225.
- **열린 질문 네 개 → Q2 · 회전 경쟁:** 답. Evidence: L1226L1226.
- **열린 질문 네 개 → Q3 · 조회 키 분리:** 답. Evidence: L1227L1227.
- **열린 질문 네 개 → Q4 · Edge 신뢰 경계:** 답. Evidence: L1228L1228.
@@ -1,18 +1,22 @@
# 열린 질문 네 개가 닿은 곳
# Question: 설계로 못 푼 질문들은 어디에서 답을 얻었는가
direction: down
direction: right
n0: "열린 질문 네 개" {
shape: rectangle
}
n1: "저장소 선택" {
n1: "Q1 · 저장소와 스키마" {
shape: rectangle
}
n2: "스키마와 조회 키" {
n2: "Q2 · 회전 경쟁" {
shape: rectangle
}
n3: "세션 스냅샷" {
n3: "Q3 · 조회 키 분리" {
shape: rectangle
}
n0 -> n1: "1차 검토"
n1 -> n2: "미해결 잔여"
n2 -> n3: "최종 잔여"
n4: "Q4 · Edge 신뢰 경계" {
shape: rectangle
}
n0 -> n1: "답"
n0 -> n2: "답"
n0 -> n3: "답"
n0 -> n4: "답"
@@ -1,12 +1,14 @@
digraph techviz {
graph [rankdir=TB, splines=ortho, nodesep=0.55, ranksep=0.85];
graph [rankdir=LR, splines=ortho, nodesep=0.55, ranksep=0.85];
node [fontname=Helvetica, fontsize=11, margin="0.18,0.12", style="rounded,filled", fillcolor=white, color="#2d4357", penwidth=1.5];
edge [fontname=Helvetica, fontsize=10, color="#364b5f", penwidth=1.4, arrowsize=0.75];
n0 [label="열린 질문 네 개", shape=box, style="rounded,filled"];
n1 [label="저장소 선택", shape=box, style="rounded,filled"];
n2 [label="스키마와 조회 키", shape=box, style="rounded,filled"];
n3 [label="세션 스냅샷", shape=box, style="rounded,filled"];
n0 -> n1 [label="1차 검토", style=solid];
n1 -> n2 [label="미해결 잔여", style=solid];
n2 -> n3 [label="최종 잔여", style=solid];
n1 [label="Q1 · 저장소와 스키마", shape=box, style="rounded,filled"];
n2 [label="Q2 · 회전 경쟁", shape=box, style="rounded,filled"];
n3 [label="Q3 · 조회 키 분리", shape=box, style="rounded,filled"];
n4 [label="Q4 · Edge 신뢰 경계", shape=box, style="rounded,filled"];
n0 -> n1 [label="", style=solid];
n0 -> n2 [label="", style=solid];
n0 -> n3 [label="답", style=solid];
n0 -> n4 [label="답", style=solid];
}
@@ -1,35 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<mxfile host="app.diagrams.net" modified="2026-07-23T00:00:00.000Z" agent="techviz-harness" version="24.7.17" type="device">
<diagram id="open-questions-answered" name="열린 질문 네 개가 닿은 곳">
<mxGraphModel dx="680" dy="677" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<mxGraphModel dx="707" dy="605" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="n_questions" value="열린 질문 네 개&lt;br/&gt;Q1 · Q2 · Q3 · Q4" tooltip="설계로는 답할 수 없었다. | Evidence: L682-L690" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;strokeColor=#2563eb;strokeWidth=2;" vertex="1" parent="1">
<mxGeometry x="73.5" y="60.0" width="153.0" height="71.0" as="geometry"/>
<mxCell id="n_questions" value="열린 질문 네 개&lt;br/&gt;Q1 · Q2 · Q3 · Q4" tooltip="설계만으로는 닫히지 않았던 Q1부터 Q4다. | Evidence: L1223-L1228" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;strokeColor=#2563eb;strokeWidth=2;" vertex="1" parent="1">
<mxGeometry x="70.0" y="274.5" width="153.0" height="71.0" as="geometry"/>
</mxCell>
<mxCell id="n_store-choice" value="저장소 선택&lt;br/&gt;Redis · PostgreSQL" tooltip="다중 인스턴스와 재시작은 여기서 풀린다. | Evidence: L682-L692" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
<mxGeometry x="70.0" y="227.0" width="160.0" height="71.0" as="geometry"/>
<mxCell id="n_q1" value="Q1 · 저장소와 스키마&lt;br/&gt;외부 저장소 · 세션 id 없는 PK" tooltip="①②는 외부 저장소로 풀리고 ③④는 기본키 문제로 남는다. | Evidence: L1225-L1225" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;strokeColor=#d97706;fillColor=#fffdf5;" vertex="1" parent="1">
<mxGeometry x="435.5" y="60.0" width="174.0" height="71.0" as="geometry"/>
</mxCell>
<mxCell id="n_schema" value="스키마와 조회 키&lt;br/&gt;키에 세션 id 없음" tooltip="덮어쓰기와 정리 누락은 여기가 원인이다. | Evidence: L682-L694" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;strokeColor=#d97706;fillColor=#fffdf5;" vertex="1" parent="1">
<mxGeometry x="75.0" y="394.0" width="150.0" height="71.0" as="geometry"/>
<mxCell id="n_q2" value="Q2 · 회전 경쟁&lt;br/&gt;이긴 새 token도 사용 불가" tooltip="경쟁이 감지되면 client session이 지워진다. | Evidence: L1226-L1226" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;strokeColor=#d97706;fillColor=#fffdf5;" vertex="1" parent="1">
<mxGeometry x="446.0" y="203.0" width="153.0" height="71.0" as="geometry"/>
</mxCell>
<mxCell id="n_snapshot" value="세션 스냅샷&lt;br/&gt;옛 클레임 값" tooltip="IdP 에서 클레임을 바꿔도 재인증까지 옛 값이 간다. | Evidence: L682-L696" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;strokeColor=#d97706;fillColor=#fffdf5;" vertex="1" parent="1">
<mxGeometry x="75.0" y="561.0" width="150.0" height="71.0" as="geometry"/>
<mxCell id="n_q3" value="Q3 · 조회 키 분리&lt;br/&gt;session id · principal" tooltip="Session과 AuthorizedClient는 각각 결정해야 한다. | Evidence: L1227-L1227" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
<mxGeometry x="428.5" y="346.0" width="188.0" height="71.0" as="geometry"/>
</mxCell>
<mxCell id="e_q-s" value="1차 검토" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_questions" target="n_store-choice">
<mxCell id="n_q4" value="Q4 · Edge 신뢰 경계&lt;br/&gt;permitAll 200 · JWT 401 · 삭제 처방 미검증" tooltip="헤더 도착과 인가 우회는 다르고 claim은 재인증까지 snapshot이다. | Evidence: L1228-L1228" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;strokeColor=#d97706;fillColor=#fffdf5;" vertex="1" parent="1">
<mxGeometry x="383.0" y="489.0" width="279.0" height="71.0" as="geometry"/>
</mxCell>
<mxCell id="e_questions-q1" value="답" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_questions" target="n_q1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="174.0" y="179.0" as="offset"/>
<mxPoint x="353.2" y="191.7" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="e_s-sc" value="미해결 잔여" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_store-choice" target="n_schema">
<mxCell id="e_questions-q2" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_questions" target="n_q2">
<mxGeometry relative="1" as="geometry">
<mxPoint x="174.0" y="346.0" as="offset"/>
<mxPoint x="358.5" y="270.6" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="e_sc-sn" value="최종 잔여" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_schema" target="n_snapshot">
<mxCell id="e_questions-q3" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_questions" target="n_q3">
<mxGeometry relative="1" as="geometry">
<mxPoint x="174.0" y="513.0" as="offset"/>
<mxPoint x="349.8" y="349.4" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="e_questions-q4" value="답" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_questions" target="n_q4">
<mxGeometry relative="1" as="geometry">
<mxPoint x="327.0" y="428.3" as="offset"/>
</mxGeometry>
</mxCell>
</root>
@@ -4,12 +4,12 @@
"source": "techviz-harness",
"elements": [
{
"id": "edge-q-s",
"id": "edge-questions-q1",
"type": "arrow",
"x": 150.0,
"y": 131.0,
"width": 0.0,
"height": 96.0,
"x": 223.0,
"y": 95.5,
"width": 212.5,
"height": 192.49,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
@@ -22,9 +22,9 @@
"frameId": null,
"index": null,
"roundness": null,
"seed": 92742789,
"seed": 832347071,
"version": 1,
"versionNonce": 717228397,
"versionNonce": 1567238899,
"isDeleted": false,
"boundElements": [],
"updated": 0,
@@ -33,19 +33,19 @@
"points": [
[
0.0,
192.49
],
[
106.25,
192.49
],
[
106.25,
0.0
],
[
0.0,
48.0
],
[
0.0,
48.0
],
[
0.0,
96.0
212.5,
0.0
]
],
"lastCommittedPoint": null,
@@ -55,7 +55,7 @@
"gap": 4
},
"endBinding": {
"elementId": "node-store-choice",
"elementId": "node-q1",
"focus": 0,
"gap": 4
},
@@ -64,10 +64,10 @@
"elbowed": true
},
{
"id": "edge-label-q-s",
"id": "edge-label-questions-q1",
"type": "text",
"x": 129.0,
"y": 167.0,
"x": 308.25,
"y": 179.745,
"width": 90,
"height": 24,
"angle": 0,
@@ -84,9 +84,9 @@
"roundness": {
"type": 3
},
"seed": 984688446,
"seed": 1467273658,
"version": 1,
"versionNonce": 114866077,
"versionNonce": 521845723,
"isDeleted": false,
"boundElements": [],
"updated": 0,
@@ -94,21 +94,21 @@
"locked": false,
"fontSize": 13,
"fontFamily": 5,
"text": "1차 검토",
"text": "",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "1차 검토",
"originalText": "",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "edge-s-sc",
"id": "edge-questions-q2",
"type": "arrow",
"x": 150.0,
"y": 298.0,
"width": 0.0,
"height": 96.0,
"x": 223.0,
"y": 238.5,
"width": 223.0,
"height": 64.16333333333336,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
@@ -121,9 +121,9 @@
"frameId": null,
"index": null,
"roundness": null,
"seed": 1449318845,
"seed": 1814066180,
"version": 1,
"versionNonce": 387238772,
"versionNonce": 259734062,
"isDeleted": false,
"boundElements": [],
"updated": 0,
@@ -132,29 +132,29 @@
"points": [
[
0.0,
64.16333333333336
],
[
111.5,
64.16333333333336
],
[
111.5,
0.0
],
[
0.0,
48.0
],
[
0.0,
48.0
],
[
0.0,
96.0
223.0,
0.0
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "node-store-choice",
"elementId": "node-questions",
"focus": 0,
"gap": 4
},
"endBinding": {
"elementId": "node-schema",
"elementId": "node-q2",
"focus": 0,
"gap": 4
},
@@ -163,10 +163,10 @@
"elbowed": true
},
{
"id": "edge-label-s-sc",
"id": "edge-label-questions-q2",
"type": "text",
"x": 129.0,
"y": 334.0,
"x": 313.5,
"y": 258.5816666666667,
"width": 90,
"height": 24,
"angle": 0,
@@ -183,9 +183,9 @@
"roundness": {
"type": 3
},
"seed": 837613550,
"seed": 96970718,
"version": 1,
"versionNonce": 1741811141,
"versionNonce": 1453223212,
"isDeleted": false,
"boundElements": [],
"updated": 0,
@@ -193,21 +193,21 @@
"locked": false,
"fontSize": 13,
"fontFamily": 5,
"text": "미해결 잔여",
"text": "",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "미해결 잔여",
"originalText": "",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "edge-sc-sn",
"id": "edge-questions-q3",
"type": "arrow",
"x": 150.0,
"y": 465.0,
"width": 0.0,
"height": 96.0,
"x": 223.0,
"y": 317.33666666666664,
"width": 205.5,
"height": 64.16333333333336,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
@@ -220,9 +220,9 @@
"frameId": null,
"index": null,
"roundness": null,
"seed": 1807543875,
"seed": 1003822894,
"version": 1,
"versionNonce": 1731809287,
"versionNonce": 307984312,
"isDeleted": false,
"boundElements": [],
"updated": 0,
@@ -234,26 +234,26 @@
0.0
],
[
0.0,
48.0
102.75,
0.0
],
[
0.0,
48.0
102.75,
64.16333333333336
],
[
0.0,
96.0
205.5,
64.16333333333336
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "node-schema",
"elementId": "node-questions",
"focus": 0,
"gap": 4
},
"endBinding": {
"elementId": "node-snapshot",
"elementId": "node-q3",
"focus": 0,
"gap": 4
},
@@ -262,10 +262,10 @@
"elbowed": true
},
{
"id": "edge-label-sc-sn",
"id": "edge-label-questions-q3",
"type": "text",
"x": 129.0,
"y": 501.0,
"x": 304.75,
"y": 337.4183333333333,
"width": 90,
"height": 24,
"angle": 0,
@@ -282,9 +282,9 @@
"roundness": {
"type": 3
},
"seed": 1702108657,
"seed": 519368116,
"version": 1,
"versionNonce": 2422163,
"versionNonce": 11227665,
"isDeleted": false,
"boundElements": [],
"updated": 0,
@@ -292,19 +292,118 @@
"locked": false,
"fontSize": 13,
"fontFamily": 5,
"text": "최종 잔여",
"text": "",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "최종 잔여",
"originalText": "",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "edge-questions-q4",
"type": "arrow",
"x": 223.0,
"y": 332.01,
"width": 160.0,
"height": 192.49,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": null,
"seed": 676540380,
"version": 1,
"versionNonce": 762820978,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"points": [
[
0.0,
0.0
],
[
80.0,
0.0
],
[
80.0,
192.49
],
[
160.0,
192.49
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "node-questions",
"focus": 0,
"gap": 4
},
"endBinding": {
"elementId": "node-q4",
"focus": 0,
"gap": 4
},
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": true
},
{
"id": "edge-label-questions-q4",
"type": "text",
"x": 282.0,
"y": 416.255,
"width": 90,
"height": 24,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": {
"type": 3
},
"seed": 690524336,
"version": 1,
"versionNonce": 1248548419,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 13,
"fontFamily": 5,
"text": "답",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "답",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "node-questions",
"type": "rectangle",
"x": 73.5,
"y": 60.0,
"x": 70.0,
"y": 274.5,
"width": 153.0,
"height": 71.0,
"angle": 0,
@@ -333,8 +432,8 @@
{
"id": "node-label-questions",
"type": "text",
"x": 83.5,
"y": 70.0,
"x": 80.0,
"y": 284.5,
"width": 133.0,
"height": 51.0,
"angle": 0,
@@ -370,11 +469,11 @@
"lineHeight": 1.25
},
{
"id": "node-store-choice",
"id": "node-q1",
"type": "rectangle",
"x": 70.0,
"y": 227.0,
"width": 160.0,
"x": 435.5,
"y": 60.0,
"width": 174.0,
"height": 71.0,
"angle": 0,
"strokeColor": "#1e1e1e",
@@ -390,9 +489,9 @@
"roundness": {
"type": 3
},
"seed": 1216537842,
"seed": 332773339,
"version": 1,
"versionNonce": 1319200332,
"versionNonce": 16416625,
"isDeleted": false,
"boundElements": [],
"updated": 0,
@@ -400,11 +499,11 @@
"locked": false
},
{
"id": "node-label-store-choice",
"id": "node-label-q1",
"type": "text",
"x": 80.0,
"y": 237.0,
"width": 140.0,
"x": 445.5,
"y": 70.0,
"width": 154.0,
"height": 51.0,
"angle": 0,
"strokeColor": "#1e1e1e",
@@ -420,9 +519,9 @@
"roundness": {
"type": 3
},
"seed": 10194117,
"seed": 400058029,
"version": 1,
"versionNonce": 1720005613,
"versionNonce": 1733135312,
"isDeleted": false,
"boundElements": [],
"updated": 0,
@@ -430,20 +529,20 @@
"locked": false,
"fontSize": 15,
"fontFamily": 5,
"text": "저장소 선택\nRedis · PostgreSQL",
"text": "Q1 · 저장소와 스키마\n외부 저장소 · 세션 id 없는 PK",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "저장소 선택\nRedis · PostgreSQL",
"originalText": "Q1 · 저장소와 스키마\n외부 저장소 · 세션 id 없는 PK",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "node-schema",
"id": "node-q2",
"type": "rectangle",
"x": 75.0,
"y": 394.0,
"width": 150.0,
"x": 446.0,
"y": 203.0,
"width": 153.0,
"height": 71.0,
"angle": 0,
"strokeColor": "#1e1e1e",
@@ -459,9 +558,9 @@
"roundness": {
"type": 3
},
"seed": 1101372798,
"seed": 1945358536,
"version": 1,
"versionNonce": 215289242,
"versionNonce": 1527216980,
"isDeleted": false,
"boundElements": [],
"updated": 0,
@@ -469,11 +568,11 @@
"locked": false
},
{
"id": "node-label-schema",
"id": "node-label-q2",
"type": "text",
"x": 85.0,
"y": 404.0,
"width": 130.0,
"x": 456.0,
"y": 213.0,
"width": 133.0,
"height": 51.0,
"angle": 0,
"strokeColor": "#1e1e1e",
@@ -489,9 +588,9 @@
"roundness": {
"type": 3
},
"seed": 1364275471,
"seed": 175294325,
"version": 1,
"versionNonce": 803678222,
"versionNonce": 854530758,
"isDeleted": false,
"boundElements": [],
"updated": 0,
@@ -499,20 +598,20 @@
"locked": false,
"fontSize": 15,
"fontFamily": 5,
"text": "스키마와 조회 키\n키에 세션 id 없음",
"text": "Q2 · 회전 경쟁\n이긴 새 token도 사용 불가",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "스키마와 조회 키\n키에 세션 id 없음",
"originalText": "Q2 · 회전 경쟁\n이긴 새 token도 사용 불가",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "node-snapshot",
"id": "node-q3",
"type": "rectangle",
"x": 75.0,
"y": 561.0,
"width": 150.0,
"x": 428.5,
"y": 346.0,
"width": 188.0,
"height": 71.0,
"angle": 0,
"strokeColor": "#1e1e1e",
@@ -528,9 +627,9 @@
"roundness": {
"type": 3
},
"seed": 706371150,
"seed": 1319437341,
"version": 1,
"versionNonce": 1013590277,
"versionNonce": 1065878780,
"isDeleted": false,
"boundElements": [],
"updated": 0,
@@ -538,11 +637,11 @@
"locked": false
},
{
"id": "node-label-snapshot",
"id": "node-label-q3",
"type": "text",
"x": 85.0,
"y": 571.0,
"width": 130.0,
"x": 438.5,
"y": 356.0,
"width": 168.0,
"height": 51.0,
"angle": 0,
"strokeColor": "#1e1e1e",
@@ -558,9 +657,9 @@
"roundness": {
"type": 3
},
"seed": 1074524942,
"seed": 862723706,
"version": 1,
"versionNonce": 330266231,
"versionNonce": 32228140,
"isDeleted": false,
"boundElements": [],
"updated": 0,
@@ -568,11 +667,80 @@
"locked": false,
"fontSize": 15,
"fontFamily": 5,
"text": "세션 스냅샷\n옛 클레임 값",
"text": "Q3 · 조회 키 분리\nsession id · principal",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "세션 스냅샷\n옛 클레임 값",
"originalText": "Q3 · 조회 키 분리\nsession id · principal",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "node-q4",
"type": "rectangle",
"x": 383.0,
"y": 489.0,
"width": 279.0,
"height": 71.0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#ffffff",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": {
"type": 3
},
"seed": 324449922,
"version": 1,
"versionNonce": 1127997445,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false
},
{
"id": "node-label-q4",
"type": "text",
"x": 393.0,
"y": 499.0,
"width": 259.0,
"height": 51.0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": {
"type": 3
},
"seed": 1668904532,
"version": 1,
"versionNonce": 1914870557,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 15,
"fontFamily": 5,
"text": "Q4 · Edge 신뢰 경계\npermitAll 200 · JWT 401 · 삭제 처방 미검증",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "Q4 · Edge 신뢰 경계\npermitAll 200 · JWT 401 · 삭제 처방 미검증",
"autoResize": true,
"lineHeight": 1.25
}
@@ -2,22 +2,22 @@
"harness_version": "0.2.0",
"spec_id": "open-questions-answered",
"spec_version": "1.1",
"spec_sha256": "761fe849313e3653d0f161bb8162d43d64389b0ec6896d249dade786b9c7915d",
"spec_sha256": "61c8b51fe77aca11e93bb8ee86cc72471b46699b7d7b7a29958650350ce5b014",
"source_context": {
"document": "docs/keycloak-session-store/final/document.md",
"document_sha256": "1d44cba1905544d92f1d26ae36a8deb64a3db3914d6b488fd30d6ae7f8cfbabe",
"document_sha256": "28aef96a2bbb94fbb10ade26a71238fee62a5a4d9fa6e7749ae98cfd0a65e560",
"anchor": {
"kind": "heading",
"value": "열린 질문 네 개에 대한 답",
"line": 680
"line": 1221
}
},
"outputs": [
"open-questions-answered.svg",
"open-questions-answered.drawio",
"open-questions-answered.mmd",
"open-questions-answered.d2",
"open-questions-answered.dot",
"open-questions-answered.drawio",
"open-questions-answered.excalidraw",
"open-questions-answered.alt.md"
],
@@ -1,10 +1,12 @@
%% 열린 질문 네 개가 닿은 곳
%% question: 설계로 못 푼 질문들은 어디에서 답을 얻었는가
flowchart TB
flowchart LR
n0["열린 질문 네 개"]
n1["저장소 선택"]
n2["스키마와 조회 키"]
n3["세션 스냅샷"]
n0 -->|"1차 검토"| n1
n1 -->|"미해결 잔여"| n2
n2 -->|"최종 잔여"| n3
n1["Q1 · 저장소와 스키마"]
n2["Q2 · 회전 경쟁"]
n3["Q3 · 조회 키 분리"]
n4["Q4 · Edge 신뢰 경계"]
n0 -->|""| n1
n0 -->|""| n2
n0 -->|"답"| n3
n0 -->|"답"| n4
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="680" height="677" viewBox="0 0 680 677" role="img" aria-labelledby="diagram-title diagram-description">
<svg xmlns="http://www.w3.org/2000/svg" width="707" height="605" viewBox="0 0 707 605" role="img" aria-labelledby="diagram-title diagram-description">
<title id="diagram-title">열린 질문 네 개가 닿은 곳</title>
<desc id="diagram-description">Q1 은 저장소를 밖으로 빼면 다중 인스턴스와 재시작은 풀리지만 덮어쓰기와 로그아웃 정리는 남는다는 답을 얻었고 원인은 기본키에 세션 id 가 없다는 것이었다. Q2 는 이긴 요청의 토큰조차 못 쓴다는 답이다. Q3 은 세션과 토큰의 조회 키가 다르므로 각각 결정해야 한다는 답이다. Q4 프록시가 헤더를 먼저 지워야 하고 IdP 의 클레임 변경은 재인증 전까지 반영되지 않는다는 답이다.</desc>
<metadata>{&quot;techviz&quot;:{&quot;spec_version&quot;:&quot;1.1&quot;,&quot;id&quot;:&quot;open-questions-answered&quot;,&quot;profile&quot;:&quot;component-flow&quot;},&quot;source_context&quot;:{&quot;document&quot;:&quot;docs/keycloak-session-store/final/document.md&quot;,&quot;document_sha256&quot;:&quot;1d44cba1905544d92f1d26ae36a8deb64a3db3914d6b488fd30d6ae7f8cfbabe&quot;,&quot;anchor&quot;:{&quot;kind&quot;:&quot;heading&quot;,&quot;value&quot;:&quot;열린 질문 네 개에 대한 답&quot;,&quot;line&quot;:680}},&quot;evidence_policy&quot;:&quot;Each factual element cites source lines or is marked assumption.&quot;,&quot;diagram_only&quot;:true}</metadata>
<desc id="diagram-description">Q1은 저장소를 밖으로 빼면 다중 인스턴스와 재시작은 풀리지만 기본키에 session id가 없어 덮어쓰기와 정리 문제가 남는다는 답이다. Q2는 refresh 경쟁이 감지되면 client session이 지워져 이긴 요청의 새 token도 쓸 수 없다는 답이다. Q3는 Application Session과 OAuth2AuthorizedClient의 조회 키가 달라 각각 저장소를 결정해야 한다는 답이다. Q4는 위조 헤더가 permitAll echo까지 도착해도 JWT 보호 경로는 401이었고, 헤더 삭제 처방은 미적용이며 IdP claim 변경은 재인증 전까지 반영되지 않는다는 답이다.</desc>
<metadata>{&quot;techviz&quot;:{&quot;spec_version&quot;:&quot;1.1&quot;,&quot;id&quot;:&quot;open-questions-answered&quot;,&quot;profile&quot;:&quot;component-flow&quot;},&quot;source_context&quot;:{&quot;document&quot;:&quot;docs/keycloak-session-store/final/document.md&quot;,&quot;document_sha256&quot;:&quot;28aef96a2bbb94fbb10ade26a71238fee62a5a4d9fa6e7749ae98cfd0a65e560&quot;,&quot;anchor&quot;:{&quot;kind&quot;:&quot;heading&quot;,&quot;value&quot;:&quot;열린 질문 네 개에 대한 답&quot;,&quot;line&quot;:1221}},&quot;evidence_policy&quot;:&quot;Each factual element cites source lines or is marked assumption.&quot;,&quot;diagram_only&quot;:true}</metadata>
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" />
@@ -49,38 +49,47 @@
.timeline-detail { font-size: 11px; fill: #4b5563; text-anchor: middle; }
</style>
</defs>
<rect class="canvas" width="680" height="677" />
<polyline class="edge kind-request style-solid emphasis-normal" points="150.0,131.0 150.0,179.0 150.0,179.0 150.0,227.0" data-evidence="682-692" />
<rect class="edge-label-bg" x="148.2" y="165.0" width="51.5" height="22" rx="3" />
<text class="edge-label" x="174.0" y="180.0">1차 검토</text>
<polyline class="edge kind-request style-solid emphasis-normal" points="150.0,298.0 150.0,346.0 150.0,346.0 150.0,394.0" data-evidence="682-694" />
<rect class="edge-label-bg" x="144.9" y="332.0" width="58.2" height="22" rx="3" />
<text class="edge-label" x="174.0" y="347.0">미해결 잔여</text>
<polyline class="edge kind-request style-solid emphasis-normal" points="150.0,465.0 150.0,513.0 150.0,513.0 150.0,561.0" data-evidence="682-696" />
<rect class="edge-label-bg" x="148.2" y="499.0" width="51.5" height="22" rx="3" />
<text class="edge-label" x="174.0" y="514.0">최종 잔여</text>
<rect class="canvas" width="707" height="605" />
<polyline class="edge kind-request style-solid emphasis-normal" points="223.0,288.0 329.2,288.0 329.2,95.5 435.5,95.5" data-evidence="1225-1225" />
<rect class="edge-label-bg" x="331.2" y="177.7" width="44.0" height="22" rx="3" />
<text class="edge-label" x="353.2" y="192.7"></text>
<polyline class="edge kind-request style-solid emphasis-normal" points="223.0,302.7 334.5,302.7 334.5,238.5 446.0,238.5" data-evidence="1226-1226" />
<rect class="edge-label-bg" x="336.5" y="256.6" width="44.0" height="22" rx="3" />
<text class="edge-label" x="358.5" y="271.6"></text>
<polyline class="edge kind-request style-solid emphasis-normal" points="223.0,317.3 325.8,317.3 325.8,381.5 428.5,381.5" data-evidence="1227-1227" />
<rect class="edge-label-bg" x="327.8" y="335.4" width="44.0" height="22" rx="3" />
<text class="edge-label" x="349.8" y="350.4"></text>
<polyline class="edge kind-request style-solid emphasis-normal" points="223.0,332.0 303.0,332.0 303.0,524.5 383.0,524.5" data-evidence="1228-1228" />
<rect class="edge-label-bg" x="305.0" y="414.3" width="44.0" height="22" rx="3" />
<text class="edge-label" x="327.0" y="429.3"></text>
<g id="node-questions">
<rect class="node-shape kind-component emphasis-primary role-source" data-evidence="682-690" x="73.5" y="60.0" width="153.0" height="71.0" rx="7" />
<text class="node-label" x="150.0" y="87.0">열린 질문 네 개</text>
<line class="node-detail-divider" x1="87.5" y1="108.0" x2="212.5" y2="108.0" />
<text class="node-detail" x="89.5" y="125.0">Q1 · Q2 · Q3 · Q4</text>
<rect class="node-shape kind-component emphasis-primary role-source" data-evidence="1223-1228" x="70.0" y="274.5" width="153.0" height="71.0" rx="7" />
<text class="node-label" x="146.5" y="301.5">열린 질문 네 개</text>
<line class="node-detail-divider" x1="84.0" y1="322.5" x2="209.0" y2="322.5" />
<text class="node-detail" x="86.0" y="339.5">Q1 · Q2 · Q3 · Q4</text>
</g>
<g id="node-store-choice">
<rect class="node-shape kind-process emphasis-normal role-control" data-evidence="682-692" x="70.0" y="227.0" width="160.0" height="71.0" rx="7" />
<text class="node-label" x="150.0" y="254.0">저장소 선택</text>
<line class="node-detail-divider" x1="84.0" y1="275.0" x2="216.0" y2="275.0" />
<text class="node-detail" x="86.0" y="292.0">Redis · PostgreSQL</text>
<g id="node-q1">
<rect class="node-shape kind-process emphasis-warning role-target" data-evidence="1225-1225" x="435.5" y="60.0" width="174.0" height="71.0" rx="7" />
<text class="node-label" x="522.5" y="87.0">Q1 · 저장소와 스키마</text>
<line class="node-detail-divider" x1="449.5" y1="108.0" x2="595.5" y2="108.0" />
<text class="node-detail" x="451.5" y="125.0">외부 저장소 · 세션 id 없는 PK</text>
</g>
<g id="node-schema">
<rect class="node-shape kind-process emphasis-warning role-control" data-evidence="682-694" x="75.0" y="394.0" width="150.0" height="71.0" rx="7" />
<text class="node-label" x="150.0" y="421.0">스키마와 조회 키</text>
<line class="node-detail-divider" x1="89.0" y1="442.0" x2="211.0" y2="442.0" />
<text class="node-detail" x="91.0" y="459.0">키에 세션 id 없음</text>
<g id="node-q2">
<rect class="node-shape kind-process emphasis-warning role-target" data-evidence="1226-1226" x="446.0" y="203.0" width="153.0" height="71.0" rx="7" />
<text class="node-label" x="522.5" y="230.0">Q2 · 회전 경쟁</text>
<line class="node-detail-divider" x1="460.0" y1="251.0" x2="585.0" y2="251.0" />
<text class="node-detail" x="462.0" y="268.0">이긴 새 token도 사용 불가</text>
</g>
<g id="node-snapshot">
<rect class="node-shape kind-process emphasis-warning role-target" data-evidence="682-696" x="75.0" y="561.0" width="150.0" height="71.0" rx="7" />
<text class="node-label" x="150.0" y="588.0">세션 스냅샷</text>
<line class="node-detail-divider" x1="89.0" y1="609.0" x2="211.0" y2="609.0" />
<text class="node-detail" x="91.0" y="626.0">옛 클레임 값</text>
<g id="node-q3">
<rect class="node-shape kind-process emphasis-normal role-target" data-evidence="1227-1227" x="428.5" y="346.0" width="188.0" height="71.0" rx="7" />
<text class="node-label" x="522.5" y="373.0">Q3 · 조회 키 분리</text>
<line class="node-detail-divider" x1="442.5" y1="394.0" x2="602.5" y2="394.0" />
<text class="node-detail" x="444.5" y="411.0">session id · principal</text>
</g>
<g id="node-q4">
<rect class="node-shape kind-process emphasis-warning role-target" data-evidence="1228-1228" x="383.0" y="489.0" width="279.0" height="71.0" rx="7" />
<text class="node-label" x="522.5" y="516.0">Q4 · Edge 신뢰 경계</text>
<line class="node-detail-divider" x1="397.0" y1="537.0" x2="648.0" y2="537.0" />
<text class="node-detail" x="399.0" y="554.0">permitAll 200 · JWT 401 · 삭제 처방 미검증</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB