{ "version": "1.1", "id": "ap3-bff-session-flow", "title": "AP3 session cookie에서 BFF downstream Bearer까지", "question": "AP3에서 브라우저의 AP3_SESSION은 어떻게 BFF가 만든 downstream Bearer 요청과 중계 JSON이 되는가?", "type": "sequence", "direction": "LR", "audience": [ "BFF의 tokenless-browser 경계를 검토하는 개발자" ], "summary": "브라우저는 session cookie만 BFF에 보내고, BFF가 server-held access token을 읽어 Resource Server용 Bearer 요청을 만든 뒤 JSON을 중계한다.", "alt": "브라우저, BFF, authorized-client store, Resource Server 사이에서 AP3_SESSION 요청, server-held token 조회, downstream Bearer 호출과 중계 JSON이 이어지는 순서도.", "long_description": "브라우저가 Authorization header 없이 AP3_SESSION cookie로 /bff/api/me를 호출한다. BFF는 현재 Authentication으로 authorized-client manager를 호출해 server-held access token을 얻고 Resource Server의 /api/me에 Bearer header를 붙인다. Resource Server가 JWT를 검증해 사용자 JSON을 반환하면 BFF가 ResponseEntity로 받아 브라우저에 중계한다. 브라우저 session cookie는 downstream으로 전달되지 않는다.", "source_context": { "document": "document.md", "document_sha256": "df4d1a604c74e756672b5b40510abfedb8c67b39af280a5f51985ea9972f5371", "anchor": { "kind": "marker", "value": "ap3-bff-session-flow", "line": 908 } }, "composition": { "profile": "sequence", "diagram_only": true, "reference_ids": [ "payment-approval-sequence" ], "rationale": "Cookie 요청, authorized-client 조회와 응답, downstream Bearer 호출과 중계 응답의 순서가 명시되어 있으므로 ordered message가 credential 변환 시점을 가장 정확히 보여 준다.", "focus_node": "bff" }, "groups": [], "nodes": [ { "id": "browser", "label": "브라우저", "kind": "participant", "role": "participant", "description": "Authorization header 없이 AP3_SESSION cookie로 BFF endpoint를 호출하고 중계 JSON을 받는다.", "evidence": [ { "start_line": 714, "end_line": 722 }, { "start_line": 750, "end_line": 760 }, { "start_line": 796, "end_line": 809 } ], "assumption": false }, { "id": "bff", "label": "Spring BFF", "kind": "participant", "role": "participant", "emphasis": "primary", "description": "Session authentication을 authorized client 조회로 바꾸고 Resource Server용 Bearer 요청을 조립한다.", "evidence": [ { "start_line": 750, "end_line": 783 }, { "start_line": 796, "end_line": 809 } ], "assumption": false }, { "id": "authorized-client-store", "label": "Authorized-client store", "kind": "participant", "role": "participant", "description": "Registration과 principal name으로 BFF의 access token과 refresh token을 보관한다.", "evidence": [ { "start_line": 700, "end_line": 712 }, { "start_line": 762, "end_line": 768 } ], "assumption": false }, { "id": "resource-server", "label": "Resource Server", "kind": "participant", "role": "participant", "description": "BFF가 붙인 Bearer JWT를 검증하고 사용자 JSON을 반환한다.", "evidence": [ { "start_line": 776, "end_line": 796 } ], "assumption": false } ], "edges": [ { "id": "session-request", "from": "browser", "to": "bff", "label": "GET /bff/api/me + AP3_SESSION", "kind": "request", "order": 1, "evidence": [ { "start_line": 750, "end_line": 760 } ], "assumption": false }, { "id": "authorize-client", "from": "bff", "to": "authorized-client-store", "label": "authorize current principal", "kind": "request", "order": 2, "evidence": [ { "start_line": 762, "end_line": 768 } ], "assumption": false }, { "id": "load-server-token", "from": "authorized-client-store", "to": "bff", "label": "server-held access token", "kind": "response", "style": "dashed", "order": 3, "evidence": [ { "start_line": 762, "end_line": 768 } ], "assumption": false }, { "id": "downstream-bearer", "from": "bff", "to": "resource-server", "label": "GET /api/me · Bearer access token", "kind": "request", "emphasis": "primary", "order": 4, "evidence": [ { "start_line": 770, "end_line": 783 } ], "assumption": false }, { "id": "resource-json", "from": "resource-server", "to": "bff", "label": "subject · username · issuer · audience", "kind": "response", "style": "dashed", "order": 5, "evidence": [ { "start_line": 785, "end_line": 796 } ], "assumption": false }, { "id": "relayed-json", "from": "bff", "to": "browser", "label": "BFF ResponseEntity → browser JSON", "kind": "response", "style": "dashed", "order": 6, "evidence": [ { "start_line": 796, "end_line": 809 } ], "assumption": false } ], "legend": [], "metadata": { "rationale": "CSRF preference 예시는 별도 concern이므로 제외하고 GET /bff/api/me의 credential 변환 순서만 표현했다." } }