{ "version": "1.1", "id": "bff-store-lookup-keys", "title": "BFF 가 저장하는 두 가지와 그 조회 키", "question": "세션 저장소를 Redis 로 옮기면 토큰도 같이 옮겨지는가", "type": "architecture", "direction": "LR", "audience": [ "Spring Boot 로 BFF 를 만드는 백엔드 엔지니어" ], "summary": "Application Session 과 OAuth2AuthorizedClient 는 서로 다른 것을 담고 서로 다른 키로 찾는다. 세션 저장소를 바꿔도 토큰은 따라오지 않는다.", "alt": "세션 id 로 찾는 Application Session 과 principal 이름으로 찾는 OAuth2AuthorizedClient 가 각각 다른 저장소에 놓인 구성.", "long_description": "Spring Security 의 자동 구성은 세션을 서블릿 컨테이너 메모리에, authorized client 를 InMemoryOAuth2AuthorizedClientService 에 둔다. 조회 경로가 다른데 이름이 비슷해 하나로 오해하기 쉽다. AuthenticatedPrincipalOAuth2AuthorizedClientRepository 는 principal 이름으로 찾고 조회 키에 session id 가 없다. 그래서 SPRING_SESSION_STORE_TYPE 을 redis 로 바꿔 세션을 옮겨도 토큰은 인스턴스 메모리에 남는다.", "source_context": { "document": "docs/keycloak-session-store/final/document.md", "document_sha256": "609353e10bfd37a9bbb6a79ecf2a32f3d3c02d5d161879a14ad4713e49e7e5e8", "anchor": { "kind": "heading", "value": "B-1 · 세션만 Redis 로 옮기면 — 반쪽만 옮겨진다", "line": 345 } }, "composition": { "profile": "component-flow", "diagram_only": true, "reference_ids": [ "payment-event-flow" ], "rationale": "두 저장 대상이 어느 키로 어느 저장소에 닿는가가 지배적 질문이다. 시간 순서가 아니라 조회 경로의 분기이므로 component-flow 를 골랐다." }, "groups": [], "nodes": [ { "id": "request", "label": "브라우저 요청", "kind": "actor", "role": "source", "emphasis": "primary", "description": "쿠키에 세션 id 를 담아 온다.", "details": [ "JSESSIONID" ], "evidence": [ { "start_line": 326, "end_line": 331 } ], "assumption": false }, { "id": "app-session", "label": "Application Session", "kind": "component", "role": "store", "emphasis": "primary", "description": "누가 로그인했는지를 담는다. 세션 id 로 찾는다.", "details": [ "조회 키: 세션 id" ], "evidence": [ { "start_line": 335, "end_line": 341 } ], "assumption": false }, { "id": "authorized-client", "label": "OAuth2AuthorizedClient", "kind": "component", "role": "store", "emphasis": "warning", "description": "access · refresh token 을 담는다. principal 이름으로 찾는다.", "details": [ "조회 키: principal 이름" ], "evidence": [ { "start_line": 335, "end_line": 341 } ], "assumption": false }, { "id": "redis", "label": "Redis", "kind": "datastore", "role": "target", "emphasis": "primary", "description": "세션을 옮긴 곳. B-1 에서 여기까지는 옮겨졌다.", "details": [ "SPRING_SESSION_STORE_TYPE=redis" ], "evidence": [ { "start_line": 347, "end_line": 351 } ], "assumption": false }, { "id": "postgres", "label": "PostgreSQL", "kind": "datastore", "role": "target", "emphasis": "primary", "description": "토큰을 옮긴 곳. B-2 에서 따로 옮겨야 했다.", "details": [ "JdbcOAuth2AuthorizedClientService" ], "evidence": [ { "start_line": 355, "end_line": 358 } ], "assumption": false } ], "edges": [ { "id": "lookup-session", "from": "request", "to": "app-session", "label": "세션 id 로 조회", "kind": "read", "evidence": [ { "start_line": 326, "end_line": 331 } ], "assumption": false }, { "id": "lookup-client", "from": "request", "to": "authorized-client", "label": "principal 이름으로 조회", "kind": "read", "evidence": [ { "start_line": 332, "end_line": 341 } ], "assumption": false }, { "id": "session-store", "from": "app-session", "to": "redis", "label": "저장", "kind": "write", "evidence": [ { "start_line": 347, "end_line": 351 } ], "assumption": false }, { "id": "client-store", "from": "authorized-client", "to": "postgres", "label": "저장", "kind": "write", "evidence": [ { "start_line": 355, "end_line": 358 } ], "assumption": false } ], "legend": [], "metadata": { "rationale": "이름이 비슷한 두 저장 대상을 조회 키로 갈랐다. B-1 과 B-2 의 결과가 이 분기에서 나온다." } }