Files
document-haness/docs/keycloak/final/.techviz/ap2-mediator-handoff-flow/spec.json
T

201 lines
5.9 KiB
JSON

{
"version": "1.1",
"id": "ap2-mediator-handoff-flow",
"title": "AP2 server-held token에서 브라우저 Bearer 요청까지",
"question": "AP2에서 AP2_SESSION으로 찾은 server-side authorized client는 어떻게 브라우저의 직접 Bearer API 호출이 되는가?",
"type": "sequence",
"direction": "LR",
"audience": [
"token mediator의 custody와 노출 경계를 검토하는 개발자"
],
"summary": "Mediator는 refresh token을 server-side store에 남기면서 현재 access token만 JSON으로 브라우저에 반환하고, 브라우저가 그 값으로 Resource Server를 직접 호출한다.",
"alt": "브라우저, Spring mediator, authorized-client store, Resource Server 사이에서 AP2_SESSION 요청, access-only 응답, 브라우저 Bearer 호출과 JSON 응답이 이어지는 순서도.",
"long_description": "브라우저가 AP2_SESSION cookie와 함께 /token/access를 Spring mediator에 요청한다. Mediator는 현재 principal과 keycloak registration으로 authorized-client store에서 token을 조회하고 access token, type, expiry만 응답한다. 브라우저는 access token을 지역 변수로 받아 Authorization Bearer header를 만들고 Resource Server의 /api/me를 직접 호출한 뒤 사용자 JSON을 받는다. Refresh token은 브라우저 응답에 포함되지 않는다.",
"source_context": {
"document": "document.md",
"document_sha256": "df4d1a604c74e756672b5b40510abfedb8c67b39af280a5f51985ea9972f5371",
"anchor": {
"kind": "marker",
"value": "ap2-mediator-handoff-flow",
"line": 645
}
},
"composition": {
"profile": "sequence",
"diagram_only": true,
"reference_ids": [
"payment-approval-sequence"
],
"rationale": "Session 요청, authorized-client 조회, access-only 응답, 브라우저 Bearer 호출과 API 응답의 순서가 명시되어 있어 ordered message가 서로 다른 왕복을 가장 명확히 분리한다.",
"focus_node": "mediator"
},
"groups": [],
"nodes": [
{
"id": "browser",
"label": "브라우저",
"kind": "participant",
"role": "participant",
"description": "AP2_SESSION으로 access endpoint를 호출하고 반환된 access token으로 Resource Server를 직접 호출한다.",
"evidence": [
{
"start_line": 505,
"end_line": 511
},
{
"start_line": 558,
"end_line": 586
}
],
"assumption": false
},
{
"id": "mediator",
"label": "Spring mediator",
"kind": "participant",
"role": "participant",
"emphasis": "primary",
"description": "현재 principal로 authorized client를 조회하고 access token, type, expiry만 JSON으로 반환한다.",
"evidence": [
{
"start_line": 503,
"end_line": 540
}
],
"assumption": false
},
{
"id": "authorized-client-store",
"label": "Authorized-client store",
"kind": "participant",
"role": "participant",
"description": "Registration과 principal name으로 access token과 refresh token을 보관하는 process-local service.",
"evidence": [
{
"start_line": 445,
"end_line": 457
},
{
"start_line": 513,
"end_line": 521
}
],
"assumption": false
},
{
"id": "resource-server",
"label": "Resource Server",
"kind": "participant",
"role": "participant",
"description": "브라우저가 만든 Bearer JWT를 검증하고 /api/me 사용자 JSON을 반환한다.",
"evidence": [
{
"start_line": 571,
"end_line": 599
}
],
"assumption": false
}
],
"edges": [
{
"id": "session-request",
"from": "browser",
"to": "mediator",
"label": "GET /token/access + AP2_SESSION",
"kind": "request",
"order": 1,
"evidence": [
{
"start_line": 503,
"end_line": 511
}
],
"assumption": false
},
{
"id": "authorize-client",
"from": "mediator",
"to": "authorized-client-store",
"label": "authorize current principal",
"kind": "request",
"order": 2,
"evidence": [
{
"start_line": 513,
"end_line": 521
}
],
"assumption": false
},
{
"id": "load-access-token",
"from": "authorized-client-store",
"to": "mediator",
"label": "current access token",
"kind": "response",
"style": "dashed",
"order": 3,
"evidence": [
{
"start_line": 513,
"end_line": 521
}
],
"assumption": false
},
{
"id": "access-only-response",
"from": "mediator",
"to": "browser",
"label": "access token + type + expiry",
"kind": "response",
"style": "dashed",
"order": 4,
"evidence": [
{
"start_line": 523,
"end_line": 540
}
],
"assumption": false
},
{
"id": "bearer-request",
"from": "browser",
"to": "resource-server",
"label": "GET /api/me · browser-created Bearer",
"kind": "request",
"emphasis": "primary",
"order": 5,
"evidence": [
{
"start_line": 558,
"end_line": 588
}
],
"assumption": false
},
{
"id": "identity-response",
"from": "resource-server",
"to": "browser",
"label": "subject · username · issuer · audience",
"kind": "response",
"style": "dashed",
"order": 6,
"evidence": [
{
"start_line": 590,
"end_line": 615
}
],
"assumption": false
}
],
"legend": [],
"metadata": {
"rationale": "Refresh token custody와 access token 전달을 혼동하지 않도록 happy-path access handoff의 메시지 순서만 표현했다."
}
}