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
@@ -0,0 +1,24 @@
# 외부 IdP 인증은 Keycloak broker에서 application credential로 다시 경계가 나뉜다
## Alternative text
Upstream IdP zone의 Google identity assertion이 Keycloak broker zone의 validation과 account linking을 거쳐 Keycloak authorization code가 되고 기존 AP1~AP4 application 경계로 이어지는 다이어그램.
## Long description
왼쪽 upstream IdP zone의 Google이 identity assertion을 Keycloak broker에 넘긴다. Keycloak은 assertion을 검증하고 provider alias와 upstream sub로 local identity를 연결한 뒤 자기 authorization code를 발급한다. 이 code가 기존 AP1·AP2·AP3·AP4 application 경계 중 하나로 이어지므로 application이 신뢰하는 issuer는 계속 Keycloak이다.
## Elements and evidence
- **Boundary: Upstream IdP** (system): Keycloak 앞에서 사용자 인증을 수행하는 외부 identity provider 경계. Evidence: L1152L1157.
- **Boundary: Keycloak → Application** (system): Keycloak이 upstream 결과를 local identity와 자기 authorization code로 바꿔 application OAuth 경계로 넘기는 구간. Evidence: L1152L1165.
- **Google IdP** (service): Upstream authorization을 수행하고 identity assertion을 Keycloak에 돌려주는 IdP. Evidence: L1152L1157.
- **Keycloak broker** (service): Upstream response를 검증하고 provider alias와 upstream sub로 local user와 session을 연결하는 broker. Evidence: L1152L1160.
- **Keycloak authorization code** (data): Upstream token이 아니라 Keycloak이 downstream application에 발급하는 authorization code. Evidence: L1161L1165.
- **AP1 · AP2 · AP3 · AP4** (service): 외부 IdP와 무관하게 기존 application credential boundary 중 하나가 이어지는 downstream 경계. Evidence: L1162L1165.
## Relationships
- **Google IdP → Keycloak broker:** identity assertion. Evidence: L1157L1158.
- **Keycloak broker → Keycloak authorization code:** issue code. Evidence: L1158L1161.
- **Keycloak authorization code → AP1 · AP2 · AP3 · AP4:** Keycloak issuer. Evidence: L1161L1165.
@@ -0,0 +1,22 @@
# 외부 IdP 인증은 Keycloak broker에서 application credential로 다시 경계가 나뉜다
# Question: 외부 IdP의 인증 결과는 Keycloak broker에서 어떻게 local identity와 Keycloak-issued authorization code로 바뀌고 기존 AP1~AP4 경계로 이어지는가?
direction: right
g0: "Upstream IdP" {
n0: "Google IdP" {
shape: rectangle
}
}
g1: "Keycloak → Application" {
n1: "Keycloak broker" {
shape: rectangle
}
n2: "Keycloak authorization code" {
shape: rectangle
}
n3: "AP1 · AP2 · AP3 · AP4" {
shape: rectangle
}
}
g0.n0 -> g1.n1: "identity assertion"
g1.n1 -> g1.n2: "issue code"
g1.n2 -> g1.n3: "Keycloak issuer"
@@ -0,0 +1,22 @@
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];
subgraph cluster_0 {
label="Upstream IdP";
style="rounded,dashed";
color="#66788a";
n0 [label="Google IdP", shape=box, style="rounded,filled"];
}
subgraph cluster_1 {
label="Keycloak → Application";
style="rounded,dashed";
color="#66788a";
n1 [label="Keycloak broker", shape=box, style="rounded,filled"];
n2 [label="Keycloak authorization code", shape=box, style="rounded,filled"];
n3 [label="AP1 · AP2 · AP3 · AP4", shape=box, style="rounded,filled"];
}
n0 -> n1 [label="identity assertion", style=solid];
n1 -> n2 [label="issue code", style=solid];
n2 -> n3 [label="Keycloak issuer", style=solid];
}
@@ -0,0 +1,44 @@
<?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="idp-broker-upstream-downstream-boundary" name="외부 IdP 인증은 Keycloak broker에서 application credential로 다시 경계가 나뉜다">
<mxGraphModel dx="1080" dy="395" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1080" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="g_upstream-zone" value="Upstream IdP" style="swimlane;html=1;rounded=1;startSize=30;horizontal=1;dashed=1;strokeWidth=1.5;fillColor=#f7f9fb;strokeColor=#66788a;fontStyle=1;fontSize=13;" vertex="1" parent="1">
<mxGeometry x="45.0" y="49.0" width="250.0" height="143.0" as="geometry"/>
</mxCell>
<mxCell id="g_broker-app-zone" value="Keycloak → Application" style="swimlane;html=1;rounded=1;startSize=30;horizontal=1;dashed=1;strokeWidth=1.5;fillColor=#f7f9fb;strokeColor=#66788a;fontStyle=1;fontSize=13;" vertex="1" parent="1">
<mxGeometry x="565.0" y="49.0" width="470.0" height="281.0" as="geometry"/>
</mxCell>
<mxCell id="n_google" value="Google IdP&lt;br/&gt;identity assertion" tooltip="Upstream authorization을 수행하고 identity assertion을 Keycloak에 돌려주는 IdP. | Evidence: L1152-L1157" 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="75.0" y="95.0" width="190.0" height="71.0" as="geometry"/>
</mxCell>
<mxCell id="n_broker" value="Keycloak broker&lt;br/&gt;broker validation&lt;br/&gt;provider alias + sub&lt;br/&gt;local user · session" tooltip="Upstream response를 검증하고 provider alias와 upstream sub로 local user와 session을 연결하는 broker. | Evidence: L1152-L1160" 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="95.0" width="190.0" height="105.0" as="geometry"/>
</mxCell>
<mxCell id="n_keycloak-code" value="Keycloak authorization code" tooltip="Upstream token이 아니라 Keycloak이 downstream application에 발급하는 authorization code. | Evidence: L1161-L1165" 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="815.0" y="95.0" width="190.0" height="74.0" as="geometry"/>
</mxCell>
<mxCell id="n_downstream" value="AP1 · AP2 · AP3 · AP4" tooltip="외부 IdP와 무관하게 기존 application credential boundary 중 하나가 이어지는 downstream 경계. | Evidence: L1162-L1165" 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="595.0" y="240.0" width="190.0" height="64.0" as="geometry"/>
</mxCell>
<mxCell id="e_b1" value="identity assertion" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_google" target="n_broker">
<mxGeometry relative="1" as="geometry">
<mxPoint x="454.0" y="139.0" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="e_b2" value="issue code" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_broker" target="n_keycloak-code">
<mxGeometry relative="1" as="geometry">
<mxPoint x="812.2" y="31.0" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="e_b3" value="Keycloak issuer" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_keycloak-code" target="n_downstream">
<mxGeometry relative="1" as="geometry">
<mxPoint x="867.5" y="322.0" as="offset"/>
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
@@ -0,0 +1,732 @@
{
"type": "excalidraw",
"version": 2,
"source": "techviz-harness",
"elements": [
{
"id": "group-upstream-zone",
"type": "rectangle",
"x": 45.0,
"y": 49.0,
"width": 250.0,
"height": 143.0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#f8f9fa",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "dashed",
"roughness": 0,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": {
"type": 3
},
"seed": 843220907,
"version": 1,
"versionNonce": 1715574101,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false
},
{
"id": "group-label-upstream-zone",
"type": "text",
"x": 61.0,
"y": 55.0,
"width": 108,
"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": 73837189,
"version": 1,
"versionNonce": 1087599900,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 14,
"fontFamily": 5,
"text": "Upstream IdP",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "Upstream IdP",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "group-broker-app-zone",
"type": "rectangle",
"x": 565.0,
"y": 49.0,
"width": 470.0,
"height": 281.0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "#f8f9fa",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "dashed",
"roughness": 0,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": {
"type": 3
},
"seed": 843169464,
"version": 1,
"versionNonce": 766837970,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false
},
{
"id": "group-label-broker-app-zone",
"type": "text",
"x": 581.0,
"y": 55.0,
"width": 198,
"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": 1618498892,
"version": 1,
"versionNonce": 1933137034,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 14,
"fontFamily": 5,
"text": "Keycloak → Application",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "Keycloak → Application",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "edge-b1",
"type": "arrow",
"x": 265.0,
"y": 130.5,
"width": 330.0,
"height": 17.0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": null,
"seed": 1881077425,
"version": 1,
"versionNonce": 1053322627,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"points": [
[
0.0,
0.0
],
[
165.0,
0.0
],
[
165.0,
17.0
],
[
330.0,
17.0
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "node-google",
"focus": 0,
"gap": 4
},
"endBinding": {
"elementId": "node-broker",
"focus": 0,
"gap": 4
},
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": true
},
{
"id": "edge-label-b1",
"type": "text",
"x": 382.0,
"y": 127.0,
"width": 144,
"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": 1939821942,
"version": 1,
"versionNonce": 1675635312,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 13,
"fontFamily": 5,
"text": "identity assertion",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "identity assertion",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "edge-b2",
"type": "arrow",
"x": 785.0,
"y": 59.0,
"width": 30.0,
"height": 88.5,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": null,
"seed": 1090225723,
"version": 1,
"versionNonce": 719927994,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"points": [
[
0.0,
88.5
],
[
30.0,
88.5
],
[
30.0,
0.0
],
[
0.0,
0.0
],
[
0.0,
64.0
],
[
30.0,
64.0
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "node-broker",
"focus": 0,
"gap": 4
},
"endBinding": {
"elementId": "node-keycloak-code",
"focus": 0,
"gap": 4
},
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": true
},
{
"id": "edge-label-b2",
"type": "text",
"x": 767.25,
"y": 19.0,
"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": 92689614,
"version": 1,
"versionNonce": 938215730,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 13,
"fontFamily": 5,
"text": "issue code",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "issue code",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "edge-b3",
"type": "arrow",
"x": 690.0,
"y": 169.0,
"width": 220.0,
"height": 181.0,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": null,
"roundness": null,
"seed": 920777401,
"version": 1,
"versionNonce": 1003519303,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"points": [
[
220.0,
0.0
],
[
220.0,
181.0
],
[
0.0,
181.0
],
[
0.0,
135.0
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "node-keycloak-code",
"focus": 0,
"gap": 4
},
"endBinding": {
"elementId": "node-downstream",
"focus": 0,
"gap": 4
},
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": true
},
{
"id": "edge-label-b3",
"type": "text",
"x": 807.5,
"y": 310.0,
"width": 120,
"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": 48429281,
"version": 1,
"versionNonce": 1797138124,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 13,
"fontFamily": 5,
"text": "Keycloak issuer",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "Keycloak issuer",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "node-google",
"type": "rectangle",
"x": 75.0,
"y": 95.0,
"width": 190.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": 1505940743,
"version": 1,
"versionNonce": 397167959,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false
},
{
"id": "node-label-google",
"type": "text",
"x": 85.0,
"y": 105.0,
"width": 170.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": 1045077956,
"version": 1,
"versionNonce": 1546820915,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 15,
"fontFamily": 5,
"text": "Google IdP\nidentity assertion",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "Google IdP\nidentity assertion",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "node-broker",
"type": "rectangle",
"x": 595.0,
"y": 95.0,
"width": 190.0,
"height": 105.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": 21942044,
"version": 1,
"versionNonce": 1744191102,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false
},
{
"id": "node-label-broker",
"type": "text",
"x": 605.0,
"y": 105.0,
"width": 170.0,
"height": 85.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": 1714928728,
"version": 1,
"versionNonce": 423573481,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 15,
"fontFamily": 5,
"text": "Keycloak broker\nbroker validation\nprovider alias + sub\nlocal user · session",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "Keycloak broker\nbroker validation\nprovider alias + sub\nlocal user · session",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "node-keycloak-code",
"type": "rectangle",
"x": 815.0,
"y": 95.0,
"width": 190.0,
"height": 74.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": 1259207617,
"version": 1,
"versionNonce": 382015936,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false
},
{
"id": "node-label-keycloak-code",
"type": "text",
"x": 825.0,
"y": 105.0,
"width": 170.0,
"height": 54.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": 1761782804,
"version": 1,
"versionNonce": 78085105,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 15,
"fontFamily": 5,
"text": "Keycloak authorization code",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "Keycloak authorization code",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "node-downstream",
"type": "rectangle",
"x": 595.0,
"y": 240.0,
"width": 190.0,
"height": 64.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": 422578816,
"version": 1,
"versionNonce": 1949897943,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false
},
{
"id": "node-label-downstream",
"type": "text",
"x": 605.0,
"y": 250.0,
"width": 170.0,
"height": 44.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": 74338888,
"version": 1,
"versionNonce": 790089632,
"isDeleted": false,
"boundElements": [],
"updated": 0,
"link": null,
"locked": false,
"fontSize": 15,
"fontFamily": 5,
"text": "AP1 · AP2 · AP3 · AP4",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "AP1 · AP2 · AP3 · AP4",
"autoResize": true,
"lineHeight": 1.25
}
],
"appState": {
"gridSize": 10,
"viewBackgroundColor": "#ffffff",
"currentItemFontFamily": 5
},
"files": {}
}
@@ -0,0 +1,32 @@
{
"harness_version": "0.2.0",
"spec_id": "idp-broker-upstream-downstream-boundary",
"spec_version": "1.1",
"spec_sha256": "9bb6a11aba2502fbe9ea9c0a0519073a1a04b9b3cae944720d592ffdc3be9511",
"source_context": {
"document": "docs/keycloak/final/document.md",
"document_sha256": "ea10df24b892e2c57123a37a4b4f0d821e4f394353e6746f50df6a48342353e9",
"anchor": {
"kind": "heading",
"value": "Google login이 들어와도 네 애플리케이션 경계는 바뀌지 않는다",
"line": 1150
}
},
"outputs": [
"idp-broker-upstream-downstream-boundary.svg",
"idp-broker-upstream-downstream-boundary.mmd",
"idp-broker-upstream-downstream-boundary.d2",
"idp-broker-upstream-downstream-boundary.dot",
"idp-broker-upstream-downstream-boundary.drawio",
"idp-broker-upstream-downstream-boundary.excalidraw",
"idp-broker-upstream-downstream-boundary.alt.md"
],
"lint_issue_count": 0,
"assumption_count": 0,
"assumptions_allowed": false,
"composition_profile": "two-zone-pipeline",
"reference_ids": [
"localization-pipeline"
],
"diagram_only": true
}
@@ -0,0 +1,14 @@
%% 외부 IdP 인증은 Keycloak broker에서 application credential로 다시 경계가 나뉜다
%% question: 외부 IdP의 인증 결과는 Keycloak broker에서 어떻게 local identity와 Keycloak-issued authorization code로 바뀌고 기존 AP1~AP4 경계로 이어지는가?
flowchart LR
subgraph g_upstream_zone["Upstream IdP"]
n0["Google IdP"]
end
subgraph g_broker_app_zone["Keycloak → Application"]
n1["Keycloak broker"]
n2["Keycloak authorization code"]
n3["AP1 · AP2 · AP3 · AP4"]
end
n0 -->|"identity assertion"| n1
n1 -->|"issue code"| n2
n2 -->|"Keycloak issuer"| n3
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="1080" height="395" viewBox="0 0 1080 395" role="img" aria-labelledby="diagram-title diagram-description">
<title id="diagram-title">외부 IdP 인증은 Keycloak broker에서 application credential로 다시 경계가 나뉜다</title>
<desc id="diagram-description">왼쪽 upstream IdP zone의 Google이 identity assertion을 Keycloak broker에 넘긴다. Keycloak은 assertion을 검증하고 provider alias와 upstream sub로 local identity를 연결한 뒤 자기 authorization code를 발급한다. 이 code가 기존 AP1·AP2·AP3·AP4 application 경계 중 하나로 이어지므로 application이 신뢰하는 issuer는 계속 Keycloak이다.</desc>
<metadata>{&quot;techviz&quot;:{&quot;spec_version&quot;:&quot;1.1&quot;,&quot;id&quot;:&quot;idp-broker-upstream-downstream-boundary&quot;,&quot;profile&quot;:&quot;two-zone-pipeline&quot;},&quot;source_context&quot;:{&quot;document&quot;:&quot;docs/keycloak/final/document.md&quot;,&quot;document_sha256&quot;:&quot;ea10df24b892e2c57123a37a4b4f0d821e4f394353e6746f50df6a48342353e9&quot;,&quot;anchor&quot;:{&quot;kind&quot;:&quot;heading&quot;,&quot;value&quot;:&quot;Google login이 들어와도 네 애플리케이션 경계는 바뀌지 않는다&quot;,&quot;line&quot;:1150}},&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="1080" height="395" />
<rect class="group-box" x="45.0" y="49.0" width="250.0" height="143.0" rx="8" />
<rect class="group-label-bg" x="59.0" y="39.0" width="106.0" height="22" />
<text class="group-label" x="69.0" y="54.0">Upstream IdP</text>
<rect class="group-box" x="565.0" y="49.0" width="470.0" height="281.0" rx="8" />
<rect class="group-label-bg" x="579.0" y="39.0" width="176.0" height="22" />
<text class="group-label" x="589.0" y="54.0">Keycloak → Application</text>
<polyline class="edge kind-data style-solid emphasis-normal" points="265.0,130.5 430.0,130.5 430.0,147.5 595.0,147.5" data-evidence="1157-1158" />
<rect class="edge-label-bg" x="384.7" y="125.0" width="138.6" height="22" rx="3" />
<text class="edge-label" x="454.0" y="140.0">identity assertion</text>
<polyline class="edge kind-data style-solid emphasis-primary" points="785.0,147.5 815.0,147.5 815.0,59.0 785.0,59.0 785.0,123.0 815.0,123.0" data-evidence="1158-1161" />
<rect class="edge-label-bg" x="769.8" y="17.0" width="85.0" height="22" rx="3" />
<text class="edge-label" x="812.2" y="32.0">issue code</text>
<polyline class="edge kind-data style-solid emphasis-normal" points="910.0,169.0 910.0,350.0 690.0,350.0 690.0,304.0" data-evidence="1161-1165" />
<rect class="edge-label-bg" x="808.2" y="308.0" width="118.5" height="22" rx="3" />
<text class="edge-label" x="867.5" y="323.0">Keycloak issuer</text>
<g id="node-google">
<rect class="node-shape kind-service emphasis-normal role-source" data-evidence="1152-1157" x="75.0" y="95.0" width="190.0" height="71.0" rx="7" />
<text class="node-label" x="170.0" y="122.0">Google IdP</text>
<line class="node-detail-divider" x1="89.0" y1="143.0" x2="251.0" y2="143.0" />
<text class="node-detail" x="91.0" y="160.0">identity assertion</text>
</g>
<g id="node-broker">
<rect class="node-shape kind-service emphasis-primary role-service" data-evidence="1152-1160" x="595.0" y="95.0" width="190.0" height="105.0" rx="7" />
<text class="node-label" x="690.0" y="122.0">Keycloak broker</text>
<line class="node-detail-divider" x1="609.0" y1="143.0" x2="771.0" y2="143.0" />
<text class="node-detail" x="611.0" y="160.0">broker validation</text>
<text class="node-detail" x="611.0" y="176.0">provider alias + sub</text>
<text class="node-detail" x="611.0" y="192.0">local user · session</text>
</g>
<g id="node-keycloak-code">
<rect class="node-shape kind-data emphasis-normal role-service" data-evidence="1161-1165" x="815.0" y="95.0" width="190.0" height="74.0" rx="7" />
<text class="node-label" x="910.0" y="122.0">Keycloak authorization</text>
<text class="node-label" x="910.0" y="140.0">code</text>
</g>
<g id="node-downstream">
<rect class="node-shape kind-service emphasis-normal role-sink" data-evidence="1162-1165" x="595.0" y="240.0" width="190.0" height="64.0" rx="7" />
<text class="node-label" x="690.0" y="270.0">AP1 · AP2 · AP3 · AP4</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.3 KiB