Files
document-haness/docs/keycloak/final/assets/credential-custody-map/credential-custody-map.svg
T

90 lines
7.4 KiB
XML

<?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>{&quot;techviz&quot;:{&quot;spec_version&quot;:&quot;1.1&quot;,&quot;id&quot;:&quot;credential-custody-map&quot;,&quot;profile&quot;:&quot;comparison&quot;},&quot;source_context&quot;:{&quot;document&quot;:&quot;document.md&quot;,&quot;document_sha256&quot;:&quot;df4d1a604c74e756672b5b40510abfedb8c67b39af280a5f51985ea9972f5371&quot;,&quot;anchor&quot;:{&quot;kind&quot;:&quot;marker&quot;,&quot;value&quot;:&quot;credential-custody-map&quot;,&quot;line&quot;:68}},&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" />
</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>