chore: readme 수정
This commit is contained in:
+16
@@ -0,0 +1,16 @@
|
||||
# Credential은 사라지지 않고 owner와 형태가 바뀐다
|
||||
|
||||
## Alternative text
|
||||
|
||||
AP1부터 AP4까지 OAuth credential 소유자, 브라우저 credential, 보관 모델과 현재 입증된 운영 범위를 같은 네 축으로 정렬한 비교 다이어그램.
|
||||
|
||||
## Long description
|
||||
|
||||
왼쪽부터 AP1, AP2, AP3, AP4를 읽는다. 각 항목은 OAuth credential 소유자, 브라우저에 남는 애플리케이션 credential, 보관 모델, shared durability나 replica 운영에 관한 현재 근거를 같은 순서로 제시한다. AP1의 access·refresh·ID token은 실행 중 JavaScript memory에 있고 persistent Web Storage 복사본만 줄인다. AP2는 refresh token을 mediator가 소유하고 server session 식별 cookie를 사용하지만 shared durable store는 입증되지 않았다. AP3는 refresh token을 BFF가 소유하며 브라우저에는 HttpOnly session과 readable XSRF token이 남는다. AP4는 minimal client-side proxy session을 사용하고 replica cookie secret 공유와 rotation은 검증되지 않았다.
|
||||
|
||||
## Elements and evidence
|
||||
|
||||
- **AP1 · browser memory** (concept): AP1은 OAuth token을 실행 중 JavaScript memory에 두며 memory-only 선택은 persistent script-readable 복사본을 줄인다. Evidence: L53–L53, L66–L66.
|
||||
- **AP2 · mediator custody** (concept): AP2는 refresh token owner를 mediator로 옮기고 server-side login state를 식별하는 cookie를 사용하지만 shared durable persistence는 입증되지 않았다. Evidence: L53–L54, L72–L72.
|
||||
- **AP3 · BFF/session** (concept): AP3는 OAuth token을 JavaScript에 주지 않고 BFF가 refresh token을 소유하지만 브라우저에는 session과 CSRF용 readable token이 남는다. Evidence: L53–L54, L64–L64, L72–L72.
|
||||
- **AP4 · proxy session** (concept): AP4는 minimal client-side proxy session을 사용하며 JavaScript는 OAuth token을 받지 않지만 replica 간 cookie secret 운영은 검증되지 않았다. Evidence: L55–L55, L64–L64, L72–L72.
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
# Credential은 사라지지 않고 owner와 형태가 바뀐다
|
||||
# Question: 네 패턴은 애플리케이션 credential을 어느 경계에 보관하며 브라우저 상태와 운영 근거에는 무엇이 남는가?
|
||||
direction: right
|
||||
n0: "AP1 · browser memory" {
|
||||
shape: rectangle
|
||||
}
|
||||
n1: "AP2 · mediator custody" {
|
||||
shape: rectangle
|
||||
}
|
||||
n2: "AP3 · BFF/session" {
|
||||
shape: rectangle
|
||||
}
|
||||
n3: "AP4 · proxy session" {
|
||||
shape: rectangle
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
digraph techviz {
|
||||
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="AP1 · browser memory", shape=box, style="rounded,filled"];
|
||||
n1 [label="AP2 · mediator custody", shape=box, style="rounded,filled"];
|
||||
n2 [label="AP3 · BFF/session", shape=box, style="rounded,filled"];
|
||||
n3 [label="AP4 · proxy session", shape=box, style="rounded,filled"];
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?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="credential-custody-map" name="Credential은 사라지지 않고 owner와 형태가 바뀐다">
|
||||
<mxGraphModel dx="1138" dy="300" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1138" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="n_ap1-browser-memory" value="AP1 · browser memory<br/>OAuth: token 3종 → JS<br/>브라우저: 실행 중 JS가 읽음<br/>보관: runtime memory<br/>운영: 영속 Web Storage 없음" tooltip="AP1은 OAuth token을 실행 중 JavaScript memory에 두며 memory-only 선택은 persistent script-readable 복사본을 줄인다. | Evidence: L53-L53, L66-L66" 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="45.0" y="45.0" width="223.0" height="122.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_ap2-mediator-custody" value="AP2 · mediator custody<br/>OAuth: refresh → mediator<br/>브라우저: session cookie<br/>보관: server-side state<br/>운영: shared store 미입증" tooltip="AP2는 refresh token owner를 mediator로 옮기고 server-side login state를 식별하는 cookie를 사용하지만 shared durable persistence는 입증되지 않았다. | Evidence: L53-L54, L72-L72" 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="320.0" y="45.0" width="223.0" height="122.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_ap3-bff-custody" value="AP3 · BFF/session<br/>OAuth: refresh → BFF<br/>브라우저: HttpOnly session<br/>보관: session + readable XSRF<br/>운영: shared store 미입증" tooltip="AP3는 OAuth token을 JavaScript에 주지 않고 BFF가 refresh token을 소유하지만 브라우저에는 session과 CSRF용 readable token이 남는다. | Evidence: L53-L54, L64-L64, L72-L72" 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="595.0" y="45.0" width="223.0" height="122.0" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="n_ap4-proxy-session" value="AP4 · proxy session<br/>OAuth: JS 미수신<br/>브라우저: proxy session<br/>보관: minimal client-side<br/>운영: secret 공유·회전 미검증" tooltip="AP4는 minimal client-side proxy session을 사용하며 JavaScript는 OAuth token을 받지 않지만 replica 간 cookie secret 운영은 검증되지 않았다. | Evidence: L55-L55, L64-L64, L72-L72" 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="870.0" y="45.0" width="223.0" height="122.0" as="geometry"/>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
+289
@@ -0,0 +1,289 @@
|
||||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "techviz-harness",
|
||||
"elements": [
|
||||
{
|
||||
"id": "node-ap1-browser-memory",
|
||||
"type": "rectangle",
|
||||
"x": 45.0,
|
||||
"y": 45.0,
|
||||
"width": 223.0,
|
||||
"height": 122.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": 222720570,
|
||||
"version": 1,
|
||||
"versionNonce": 40282148,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-ap1-browser-memory",
|
||||
"type": "text",
|
||||
"x": 55.0,
|
||||
"y": 55.0,
|
||||
"width": 203.0,
|
||||
"height": 102.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": 983051494,
|
||||
"version": 1,
|
||||
"versionNonce": 1811623656,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "AP1 · browser memory\nOAuth: token 3종 → JS\n브라우저: 실행 중 JS가 읽음\n보관: runtime memory\n운영: 영속 Web Storage 없음",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "AP1 · browser memory\nOAuth: token 3종 → JS\n브라우저: 실행 중 JS가 읽음\n보관: runtime memory\n운영: 영속 Web Storage 없음",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-ap2-mediator-custody",
|
||||
"type": "rectangle",
|
||||
"x": 320.0,
|
||||
"y": 45.0,
|
||||
"width": 223.0,
|
||||
"height": 122.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": 1325947112,
|
||||
"version": 1,
|
||||
"versionNonce": 219900650,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-ap2-mediator-custody",
|
||||
"type": "text",
|
||||
"x": 330.0,
|
||||
"y": 55.0,
|
||||
"width": 203.0,
|
||||
"height": 102.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": 1709863446,
|
||||
"version": 1,
|
||||
"versionNonce": 31489928,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "AP2 · mediator custody\nOAuth: refresh → mediator\n브라우저: session cookie\n보관: server-side state\n운영: shared store 미입증",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "AP2 · mediator custody\nOAuth: refresh → mediator\n브라우저: session cookie\n보관: server-side state\n운영: shared store 미입증",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-ap3-bff-custody",
|
||||
"type": "rectangle",
|
||||
"x": 595.0,
|
||||
"y": 45.0,
|
||||
"width": 223.0,
|
||||
"height": 122.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": 1229486250,
|
||||
"version": 1,
|
||||
"versionNonce": 1938967200,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-ap3-bff-custody",
|
||||
"type": "text",
|
||||
"x": 605.0,
|
||||
"y": 55.0,
|
||||
"width": 203.0,
|
||||
"height": 102.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": 1526462817,
|
||||
"version": 1,
|
||||
"versionNonce": 1290302415,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "AP3 · BFF/session\nOAuth: refresh → BFF\n브라우저: HttpOnly session\n보관: session + readable XSRF\n운영: shared store 미입증",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "AP3 · BFF/session\nOAuth: refresh → BFF\n브라우저: HttpOnly session\n보관: session + readable XSRF\n운영: shared store 미입증",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
},
|
||||
{
|
||||
"id": "node-ap4-proxy-session",
|
||||
"type": "rectangle",
|
||||
"x": 870.0,
|
||||
"y": 45.0,
|
||||
"width": 223.0,
|
||||
"height": 122.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": 756651226,
|
||||
"version": 1,
|
||||
"versionNonce": 818671235,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "node-label-ap4-proxy-session",
|
||||
"type": "text",
|
||||
"x": 880.0,
|
||||
"y": 55.0,
|
||||
"width": 203.0,
|
||||
"height": 102.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": 1227962233,
|
||||
"version": 1,
|
||||
"versionNonce": 883345119,
|
||||
"isDeleted": false,
|
||||
"boundElements": [],
|
||||
"updated": 0,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 15,
|
||||
"fontFamily": 5,
|
||||
"text": "AP4 · proxy session\nOAuth: JS 미수신\n브라우저: proxy session\n보관: minimal client-side\n운영: secret 공유·회전 미검증",
|
||||
"textAlign": "center",
|
||||
"verticalAlign": "middle",
|
||||
"containerId": null,
|
||||
"originalText": "AP4 · proxy session\nOAuth: JS 미수신\n브라우저: proxy session\n보관: minimal client-side\n운영: secret 공유·회전 미검증",
|
||||
"autoResize": true,
|
||||
"lineHeight": 1.25
|
||||
}
|
||||
],
|
||||
"appState": {
|
||||
"gridSize": 10,
|
||||
"viewBackgroundColor": "#ffffff",
|
||||
"currentItemFontFamily": 5
|
||||
},
|
||||
"files": {}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"harness_version": "0.2.0",
|
||||
"spec_id": "credential-custody-map",
|
||||
"spec_version": "1.1",
|
||||
"spec_sha256": "f0c0866b8d1a0d6a485c88d4514b35e935a10b9d6d29e446b4ada7b1ee2e5a40",
|
||||
"source_context": {
|
||||
"document": "document.md",
|
||||
"document_sha256": "df4d1a604c74e756672b5b40510abfedb8c67b39af280a5f51985ea9972f5371",
|
||||
"anchor": {
|
||||
"kind": "marker",
|
||||
"value": "credential-custody-map",
|
||||
"line": 68
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
"credential-custody-map.svg",
|
||||
"credential-custody-map.drawio",
|
||||
"credential-custody-map.mmd",
|
||||
"credential-custody-map.d2",
|
||||
"credential-custody-map.dot",
|
||||
"credential-custody-map.excalidraw",
|
||||
"credential-custody-map.alt.md"
|
||||
],
|
||||
"lint_issue_count": 0,
|
||||
"assumption_count": 0,
|
||||
"assumptions_allowed": false,
|
||||
"composition_profile": "comparison",
|
||||
"reference_ids": [
|
||||
"contract-comparison"
|
||||
],
|
||||
"diagram_only": true
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
%% Credential은 사라지지 않고 owner와 형태가 바뀐다
|
||||
%% question: 네 패턴은 애플리케이션 credential을 어느 경계에 보관하며 브라우저 상태와 운영 근거에는 무엇이 남는가?
|
||||
flowchart LR
|
||||
n0["AP1 · browser memory"]
|
||||
n1["AP2 · mediator custody"]
|
||||
n2["AP3 · BFF/session"]
|
||||
n3["AP4 · proxy session"]
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1138" height="300" viewBox="0 0 1138 300" role="img" aria-labelledby="diagram-title diagram-description">
|
||||
<title id="diagram-title">Credential은 사라지지 않고 owner와 형태가 바뀐다</title>
|
||||
<desc id="diagram-description">왼쪽부터 AP1, AP2, AP3, AP4를 읽는다. 각 항목은 OAuth credential 소유자, 브라우저에 남는 애플리케이션 credential, 보관 모델, shared durability나 replica 운영에 관한 현재 근거를 같은 순서로 제시한다. AP1의 access·refresh·ID token은 실행 중 JavaScript memory에 있고 persistent Web Storage 복사본만 줄인다. AP2는 refresh token을 mediator가 소유하고 server session 식별 cookie를 사용하지만 shared durable store는 입증되지 않았다. AP3는 refresh token을 BFF가 소유하며 브라우저에는 HttpOnly session과 readable XSRF token이 남는다. AP4는 minimal client-side proxy session을 사용하고 replica cookie secret 공유와 rotation은 검증되지 않았다.</desc>
|
||||
<metadata>{"techviz":{"spec_version":"1.1","id":"credential-custody-map","profile":"comparison"},"source_context":{"document":"document.md","document_sha256":"df4d1a604c74e756672b5b40510abfedb8c67b39af280a5f51985ea9972f5371","anchor":{"kind":"marker","value":"credential-custody-map","line":68}},"evidence_policy":"Each factual element cites source lines or is marked assumption.","diagram_only":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" />
|
||||
</marker>
|
||||
<style>
|
||||
:root { color-scheme: light; }
|
||||
text { font-family: Inter, Pretendard, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #111827; }
|
||||
.canvas { fill: #ffffff; }
|
||||
.group-box { fill: #ffffff; stroke: #9ca3af; stroke-width: 1.4; stroke-dasharray: 7 5; }
|
||||
.group-label-bg { fill: #ffffff; }
|
||||
.group-label { font-size: 13px; font-weight: 650; fill: #374151; }
|
||||
.edge { fill: none; stroke: #374151; stroke-width: 1.8; stroke-linejoin: round; stroke-linecap: round; marker-end: url(#arrow); }
|
||||
.edge.style-dashed, .edge.semantic-dashed, .edge.assumption { stroke-dasharray: 7 5; }
|
||||
.edge.style-dotted { stroke-dasharray: 2 5; }
|
||||
.edge.emphasis-primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.edge.emphasis-muted { stroke: #9ca3af; }
|
||||
.edge.emphasis-warning, .edge.kind-failure, .edge.kind-error { stroke: #dc2626; stroke-width: 2.2; }
|
||||
.edge-label-bg { fill: #ffffff; }
|
||||
.edge-label { font-size: 12px; font-weight: 560; text-anchor: middle; }
|
||||
.node-shape { fill: #ffffff; stroke: #4b5563; stroke-width: 1.7; }
|
||||
.node-shape.emphasis-primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.node-shape.emphasis-muted { stroke: #9ca3af; fill: #f9fafb; }
|
||||
.node-shape.emphasis-warning { stroke: #d97706; stroke-width: 2; fill: #fffdf5; }
|
||||
.node-shape.kind-database, .node-shape.kind-datastore, .node-shape.kind-storage { fill: #f8fafc; }
|
||||
.node-shape.kind-queue, .node-shape.kind-event, .node-shape.kind-topic { fill: #fafafa; }
|
||||
.node-shape.assumption { stroke-dasharray: 4 4; }
|
||||
.storage-bottom, .controller-divider { fill: none; stroke: #4b5563; stroke-width: 1.4; }
|
||||
.controller-led { fill: #4b5563; }
|
||||
.actor-symbol { fill: none; stroke: #4b5563; stroke-width: 1.8; stroke-linecap: round; }
|
||||
.actor-symbol.emphasis-primary { stroke: #2563eb; stroke-width: 2.2; }
|
||||
.node-label { font-size: 14px; font-weight: 650; text-anchor: middle; }
|
||||
.node-role { font-size: 10px; letter-spacing: 0.04em; text-anchor: middle; fill: #6b7280; }
|
||||
.node-detail-divider { stroke: #d1d5db; stroke-width: 1; }
|
||||
.node-detail { font-size: 11px; fill: #374151; }
|
||||
.assumption-badge { font-size: 9px; font-weight: 700; fill: #92400e; }
|
||||
.failure-mark { stroke: #dc2626; stroke-width: 4; stroke-linecap: round; }
|
||||
.lifeline { stroke: #9ca3af; stroke-width: 1.2; stroke-dasharray: 5 5; }
|
||||
.timeline-axis { stroke: #374151; stroke-width: 1.8; marker-end: url(#arrow); }
|
||||
.timeline-stem { stroke: #6b7280; stroke-width: 1.3; }
|
||||
.timeline-marker { fill: #ffffff; stroke: #374151; stroke-width: 1.7; }
|
||||
.timeline-marker.primary { fill: #2563eb; stroke: #2563eb; }
|
||||
.timeline-marker.warning { fill: #dc2626; stroke: #dc2626; }
|
||||
.timeline-label { font-size: 13px; font-weight: 650; text-anchor: middle; }
|
||||
.timeline-detail { font-size: 11px; fill: #4b5563; text-anchor: middle; }
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="canvas" width="1138" height="300" />
|
||||
<g id="node-ap1-browser-memory">
|
||||
<rect class="node-shape kind-concept emphasis-warning role-option" data-evidence="53-53,66-66" x="45.0" y="45.0" width="223.0" height="122.0" rx="7" />
|
||||
<text class="node-label" x="156.5" y="72.0">AP1 · browser memory</text>
|
||||
<line class="node-detail-divider" x1="59.0" y1="93.0" x2="254.0" y2="93.0" />
|
||||
<text class="node-detail" x="61.0" y="110.0">OAuth: token 3종 → JS</text>
|
||||
<text class="node-detail" x="61.0" y="126.0">브라우저: 실행 중 JS가 읽음</text>
|
||||
<text class="node-detail" x="61.0" y="142.0">보관: runtime memory</text>
|
||||
<text class="node-detail" x="61.0" y="158.0">운영: 영속 Web Storage 없음</text>
|
||||
</g>
|
||||
<g id="node-ap2-mediator-custody">
|
||||
<rect class="node-shape kind-concept emphasis-normal role-option" data-evidence="53-54,72-72" x="320.0" y="45.0" width="223.0" height="122.0" rx="7" />
|
||||
<text class="node-label" x="431.5" y="72.0">AP2 · mediator custody</text>
|
||||
<line class="node-detail-divider" x1="334.0" y1="93.0" x2="529.0" y2="93.0" />
|
||||
<text class="node-detail" x="336.0" y="110.0">OAuth: refresh → mediator</text>
|
||||
<text class="node-detail" x="336.0" y="126.0">브라우저: session cookie</text>
|
||||
<text class="node-detail" x="336.0" y="142.0">보관: server-side state</text>
|
||||
<text class="node-detail" x="336.0" y="158.0">운영: shared store 미입증</text>
|
||||
</g>
|
||||
<g id="node-ap3-bff-custody">
|
||||
<rect class="node-shape kind-concept emphasis-primary role-option" data-evidence="53-54,64-64,72-72" x="595.0" y="45.0" width="223.0" height="122.0" rx="7" />
|
||||
<text class="node-label" x="706.5" y="72.0">AP3 · BFF/session</text>
|
||||
<line class="node-detail-divider" x1="609.0" y1="93.0" x2="804.0" y2="93.0" />
|
||||
<text class="node-detail" x="611.0" y="110.0">OAuth: refresh → BFF</text>
|
||||
<text class="node-detail" x="611.0" y="126.0">브라우저: HttpOnly session</text>
|
||||
<text class="node-detail" x="611.0" y="142.0">보관: session + readable XSRF</text>
|
||||
<text class="node-detail" x="611.0" y="158.0">운영: shared store 미입증</text>
|
||||
</g>
|
||||
<g id="node-ap4-proxy-session">
|
||||
<rect class="node-shape kind-concept emphasis-normal role-option" data-evidence="55-55,64-64,72-72" x="870.0" y="45.0" width="223.0" height="122.0" rx="7" />
|
||||
<text class="node-label" x="981.5" y="72.0">AP4 · proxy session</text>
|
||||
<line class="node-detail-divider" x1="884.0" y1="93.0" x2="1079.0" y2="93.0" />
|
||||
<text class="node-detail" x="886.0" y="110.0">OAuth: JS 미수신</text>
|
||||
<text class="node-detail" x="886.0" y="126.0">브라우저: proxy session</text>
|
||||
<text class="node-detail" x="886.0" y="142.0">보관: minimal client-side</text>
|
||||
<text class="node-detail" x="886.0" y="158.0">운영: secret 공유·회전 미검증</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.4 KiB |
Reference in New Issue
Block a user