--- title: branch / feature-keycloak-bff-vs-spa-direct (BFF 대안 비교 — SPA Direct vs Backend-for-Frontend) source_type: branch-note status: raw id: BR-KEYCLOAK-CHILD-2BFCDCAB kind: branch-child project: keycloak-patterns-overview work_item: WI-KEYCLOAK-PATTERNS-OVERVIEW-020 inherits: [DEC-KEYCLOAK-PATTERNS-OVERVIEW-AUTH-TAXONOMY-001@1] refines: [] overrides: [] depends_on: [] contract_packet: 1 branch: feature-keycloak-bff-vs-spa-direct parent_branch: feature-keycloak-patterns related_projects: [keycloak-patterns] tags: [branch, keycloak-patterns, p2a, bff, spa, xss-surface, session, oauth2-login] created: 2026-05-25 target_merge: status_label: in-progress contract_packet_sha256: f0b2fdad99f29c8e633580dc9d5b65c5b88879f90ace63f773a6ded847306d5f --- # branch: feature-keycloak-bff-vs-spa-direct — BFF 대안 비교 > Layer: `raw/branch-notes/` — [[raw/branch-notes/feature-keycloak-patterns]]의 branch-child. > **목적**: P2A(SPA Direct, 토큰을 SPA가 보유) vs BFF(Backend-for-Frontend, 토큰을 백엔드가 보유)의 **XSS surface 차이**와 stateful trade-off를 명확히 정리. > `status_label`: `in-progress` | `review` | `merged` | `abandoned` ## 부모 (필수) [[raw/branch-notes/feature-keycloak-patterns]] ## 브랜치 계약 패킷 - **생성 시 프로젝트 개정**: `1` - **패킷 스키마**: `contract_packet: 1` - **완료 조건**: project governance hub가 AP1~AP4 taxonomy와 child progress index를 유지한다 ### 상속한 프로젝트 결정 | Decision Ref | Project Summary | Branch Application | Source | |---|---|---|---| | `DEC-KEYCLOAK-PATTERNS-OVERVIEW-AUTH-TAXONOMY-001@1` | canonical 분류축은 AP1~AP4 인증 통합 아키텍처와 cross-cutting 변형이다 | SPA Direct와 BFF 비교를 AP taxonomy의 대안 근거로 유지한다 | [[raw/project-notes/keycloak-patterns-overview]] | ### 브랜치 지역 결정 | Decision ID | Decision | Relation | Supporting Claims | Status | |---|---|---|---|---| | D1 | 학습 baseline은 SPA Direct로 둔다 | `local` | [[raw/project-notes/keycloak-patterns-overview]] | `proposed` | | D2 | BFF 구현은 비교 문서 범위로 제한한다 | `local` | [[raw/project-notes/keycloak-patterns-overview]] | `proposed` | | D3 | native client는 별도 PKCE 흐름이 필요함을 기록한다 | `local` | [[raw/project-notes/keycloak-patterns-overview]] | `proposed` | | D4 | browser token XSS surface를 BFF motivation으로 기록한다 | `local` | [[raw/project-notes/keycloak-patterns-overview]] | `proposed` | | D5 | BFF 권고의 적용 조건을 client credential 사용 여부로 제한한다 | `local` | [[raw/project-notes/keycloak-patterns-overview]] | `proposed` | ### 선언한 예외 | Override ID | Overrides | Reason | Approval | Status | |---|---|---|---|---| 없음. ## 목표 P2A SPA Direct는 OIDC + PKCE의 canonical 흐름이지만 **토큰이 브라우저(JS 컨텍스트)에 존재**한다는 근본적 위험이 있다. BFF는 이 위험을 제거하는 변형 — 백엔드가 OAuth client 역할을 하고, 브라우저는 httpOnly session cookie만 보유. Curity 등 보안 벤더가 권고하는 패턴. 핵심 질문: - BFF 아키텍처에서 토큰이 흐르는 경계는? 누가 보관하는가? - Spring Security `oauth2Login` + session vs Spring Authorization Server (AS 자체 구축) 차이? - BFF 단점은? (stateful, scale-out 시 session sharing 필요) - 어떤 기준으로 SPA Direct vs BFF를 결정하는가? (XSS 민감도 / 모바일 클라이언트 유무 / 운영 복잡도) 본 sub-sub-branch는 **아키텍처 다이어그램 + Spring 구현 옵션 + 결정 기준 매트릭스**를 정리. - 이슈: (학습 노트, 이슈 없음) - PR: (구현 없음) ## 범위 ### 포함 범위 - BFF(Backend-for-Frontend)와 SPA Direct(P2A)의 **XSS surface 차이** 정리 — 토큰이 브라우저(JS 컨텍스트)에 있는가 vs 백엔드에 있는가의 경계 - BFF 아키텍처 다이어그램 + 토큰 흐름 경계(누가 access/refresh token holder 인가) - Spring Security `oauth2Login` (BFF) 구현 옵션의 **사전 명세** — `documented-only` (실 구현 아님, §구현 가이드) - **SPA Direct vs BFF 결정 기준 매트릭스** — "언제 어느 패턴" 선택 조건 (XSS 민감도 / 모바일 클라이언트 유무 / 백엔드 stateless / 운영 복잡도 / revocation 즉시성 / OAuth 2.1의 client-credentials 조건부 권고) - Curity(company-tech-blog) + OAuth 2.1 draft(official-standard) 인용으로 BFF motivation 근거화 ### 제외 범위 > 의도적으로 제외한 것. 면접 등에서 "이건 범위에 없었습니다"라고 답할 근거. - **BFF 실 구현/배포** — 문서화만. `documented-only` 유지(D2). P3A 실 구현 이후 XSS 민감 요구 발생 시 별도 확장 branch - **Spring Authorization Server (AS 자체 구축)** — Keycloak 대체 프로젝트로 BFF 결정과 직교. Keycloak을 AS로 두고 `oauth2Login`만으로 BFF 성립하므로 본 학습 범위 밖 - **P1A Edge ForwardAuth 상세** — 형제 [[raw/branch-notes/feature-keycloak-edge-forwardauth-no-google]] 소관 (본 branch는 BFF와의 개념 구분만) - **SPA Direct 토큰 저장 위치 상세** — 형제 [[raw/branch-notes/feature-keycloak-spa-token-storage-tradeoff]] 소관 - **refresh token rotation/revocation 상세** — 형제 [[raw/branch-notes/feature-keycloak-refresh-token-rotation]] 소관 - **모바일/native OAuth client 실 흐름** — RFC 8252 public-client PKCE 흐름 자체의 구현. 본 branch는 "BFF가 모바일을 커버 못 함"의 **한계 명시**까지만(D3) ## 근거 (필수, 최소 1개+) - [[raw/company-tech-blogs/curity-bff-pattern-spa]] — Curity BFF pattern article (메인 근거) - [[raw/official-docs/oauth-v2-1-draft-ietf]] — OAuth 2.1 draft (browser app이 client credentials를 사용하려는 경우의 BFF 조건부 권고) - [[raw/official-docs/spring-security-resource-server-jwt]] — Spring Security (SPA Direct 측 비교 reference) ## TODO 각 항목 옆에 증거 등급 표기: `actually-implemented` | `locally-verified` | `prod-verified` | `documented-only` | `planned` | `needs-confirmation` - [ ] **BFF 아키텍처 다이어그램** — 등급: `planned` ```text ┌─────────────────┐ │ Keycloak │ └────────▲────────┘ │ OIDC (server-side) │ access/refresh │ token 보유 ┌────────┴────────┐ Browser (SPA) ── httpOnly session ────► │ BFF (Backend) │ ── Bearer token ──► Resource API cookie (JSESSIONID 등) │ - session store│ (BFF가 token │ - token cache │ holder) └─────────────────┘ ``` - 브라우저: 토큰 0개, session cookie만 - BFF: OAuth client 역할 + session ↔ token mapping 보관 (in-memory / Redis) - [ ] **Spring Security `oauth2Login` 구현 옵션** — 등급: `documented-only` - 의존성: `spring-boot-starter-oauth2-client` - `application.yml`: ```yaml spring: security: oauth2: client: registration: keycloak: client-id: bff-client client-secret: authorization-grant-type: authorization_code redirect-uri: "{baseUrl}/login/oauth2/code/keycloak" scope: openid, profile, email provider: keycloak: issuer-uri: https:///realms/ ``` - `http.oauth2Login(...)` + `http.sessionManagement(...)` (stateful session) - 백엔드가 자동으로 authorization code flow 수행 + session 생성 + `OAuth2AuthorizedClient`에 토큰 보관 - [ ] **Spring Authorization Server 대안** — 등급: `documented-only` - Spring Authorization Server는 **AS 자체를 직접 구축**하는 프로젝트 (Keycloak 대체). BFF와 직교한 결정. - BFF 본질은 "백엔드가 OAuth client" — Keycloak을 AS로 두고 Spring `oauth2Login`만으로 충분. - 본 P2A 학습 범위 외 (Keycloak 대체 안 함) - [ ] **BFF 단점** — 등급: `documented-only` - **Stateful**: session store 필요 → scale-out 시 sticky session 또는 Redis 등 외부 session store - **모바일 클라이언트**: BFF는 web SPA 전용. 모바일은 별도 OAuth client 흐름 필요 → BFF가 모바일까지 커버하려면 추가 endpoint 설계 - **CSRF surface 증가**: session cookie 자동 첨부 → CSRF token 또는 SameSite 필요 - **운영 복잡도**: session store 장애 시 전체 로그인 무효화 - [ ] **결정 기준 매트릭스** — 등급: `documented-only` | 기준 | SPA Direct (P2A) 우위 | BFF 우위 | |------|----------------------|----------| | XSS 민감도 (금융/의료) | — | ✅ | | 모바일/네이티브 동일 흐름 | ✅ | — | | 백엔드 stateless 유지 | ✅ | — | | 운영 단순성 (session store 불필요) | ✅ | — | | 토큰 revocation 즉시성 | — | ✅ (session 종료) | | OAuth 2.1 draft 권고 | ✅ public client + PKCE | ✅ client credentials가 필요한 browser app (D5) | | 다중 backend microservice | ✅ (각자 JWT 검증) | △ (BFF가 fan-out) | - [ ] **Curity / OAuth 2.1 draft 인용** — 등급: `documented-only` - Curity: *"The only way to protect tokens from being accessed by any malicious code is to keep them away from the browser."* - OAuth 2.1 draft §2.1: browser-based app 이 **client credentials 를 사용하려는 경우** BFF 패턴을 **권고** (`OA21-C4` — "browser-based app 전반 의무" 아님, 조건부) ## 진행 중 메모 작업하며 떠오른 메모. 자유 형식. - BFF는 "토큰을 백엔드에 두는 OAuth client" 패턴. P1A(Edge ForwardAuth)와 헷갈리기 쉬운데 두 가지가 다름: - P1A는 reverse proxy가 인증 검문소(별도 컴포넌트 oauth2-proxy) - BFF는 application backend 자체가 OAuth client + session holder - Spring Security `oauth2Login`은 본질적으로 BFF 패턴을 자동 구현해 줌. SPA Direct와 다른 starter(`oauth2-client` vs `oauth2-resource-server`)를 쓴다는 점이 명확한 분기점. ## 결정 사항 (decisions) > 추후 면접/회고에서 "왜 이렇게 했나" 답할 근거. 대안과 함께 기록. - 2026-05-25: 본 keycloak-patterns 프로젝트는 **SPA Direct (P2A)를 학습 목적의 1순위**로 채택. BFF는 비교 문서로만 정리. 이유: canonical OIDC + PKCE 흐름을 먼저 이해하는 것이 목표. - 2026-05-25: BFF 실 구현은 본 sub-sub-branch 범위 외 — SSOT §8 자신 없는 부분에 BFF 미경험으로 명시되어 있고, P3A 구현 이후 별도 확장 시 고려. - 2026-05-25: BFF의 모바일 한계는 분명히 기록 (P2A 형제 branch에서 다중 클라이언트 장점을 활용한 결정과 연결). ## 결정-근거 매핑 > 각 결정이 어떤 raw source claim 으로 뒷받침되는지. company-tech-blog 인 Curity 는 `company-case-study` 강도이며, OAuth 2.1 draft (`official-standard`) 와 Keycloak 공식 doc (`official-vendor-doc`) 으로만 official best practice 단언 가능. 단독 company-tech-blog 만으로는 official 단언 금지. > `선택 조건` 열(R2): "이 조건일 때 이 결정, 다른 조건이면 어떤 대안". 본 branch 는 SPA Direct vs BFF 선택 자체가 주제이므로 각 결정의 선택 기준을 명시한다. | Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk | |---|---|---|---|---|---| | D1 | 본 keycloak-patterns 프로젝트는 SPA Direct (P2A) 를 학습 1순위로 채택, BFF 는 비교 문서로만 정리 | **canonical OIDC + PKCE 흐름 학습이 1차 목표**일 때 SPA Direct. XSS 민감 데이터(금융/의료) 운영 요구가 우선이면 BFF 를 1순위로 전환 | `raw/official-docs/oauth-v2-1-draft-ietf.md#OA21-C1` (PKCE MUST — canonical SPA Direct 흐름), `raw/official-docs/oauth-v2-1-draft-ietf.md#OA21-C4` (BFF 권고는 client credentials 사용 시) | `official-standard` | P2A SPA Direct 가 OAuth 2.1 §2.1 의 "client credentials 없는 public client + PKCE" 시나리오에 정합한지 본 프로젝트 client 설정 (`Standard Flow + Public + PKCE S256`) 으로 실 검증 필요 | | D2 | BFF 실 구현은 본 sub-sub-branch 범위 외 (`documented-only` 유지) | **학습 우선순위/시간 제약** 하에서는 문서화만. P3A 실 구현 완료 + XSS 민감 요구 발생 시 별도 확장 branch 로 실 구현 | UNSUPPORTED_DECISION (운영 결정 — 학습 우선순위 / 시간 제약 사유, 외부 자료가 직접 뒷받침하지 않음) | UNSUPPORTED_DECISION | 미구현 상태에서 면접/포트폴리오에 BFF 경험을 주장하면 거짓. 본 branch 의 모든 BFF 관련 등급은 `documented-only` 로 유지해야 함 | | D3 | BFF 의 모바일 한계 (모바일은 별도 OAuth client 흐름 필요) 를 명시적으로 기록 | **web SPA 단일 클라이언트**면 BFF 성립. 모바일/native 클라이언트가 공존하면 native 는 별도 public-client PKCE 흐름(RFC 8252)이 MUST → BFF 단독으로 커버 불가, SPA Direct 가 다중 클라이언트에 유리 | `raw/official-docs/security-oauth2-pkce-rfc-8252.md#RFC8252-C1` (native public client 는 자체 PKCE 흐름 MUST — "별도 흐름 필요" 절반을 corroborate) | `official-standard` (부분 — "모바일은 별도 흐름 필요"만 근거; "BFF 가 모바일에서 동작 불가"는 여전히 추론) | RFC8252-C1 은 native 가 자체 PKCE 흐름을 MUST 사용함을 보장할 뿐, "BFF session cookie 모델이 모바일에서 동작 안 한다"는 절대 표현은 직접 없음. 모바일 SDK 측 cookie 처리 / native browser handoff 는 별도 검증(Claims To Verify) 필요 | | D4 | "토큰을 브라우저 밖에 두는 것이 XSS 로부터 보호하는 방법" 이라는 BFF motivation 인용 | XSS 위협 모델이 유의(브라우저에 token 존재 = 탈취 표면)한 SPA 일 때 이 motivation 이 BFF 채택 근거. XSS 표면이 무의미할 만큼 통제(CSP/sanitization)되면 SPA Direct 도 허용 | `raw/company-tech-blogs/curity-bff-pattern-spa.md#CURITY-BFF-C1`, `raw/company-tech-blogs/curity-bff-pattern-spa.md#CURITY-BFF-C3`, `raw/company-tech-blogs/curity-bff-pattern-spa.md#CURITY-BFF-C4` | `company-case-study` | Curity 는 vendor 이며 본 인용은 official best practice 가 아님. "유일한 방법" 표현은 vendor 의 강한 주장 — OAuth 2.1 `OA21-C4` 로만 official 권고 corroborate 가능 | | D5 | OAuth 2.1 draft 가 browser-based app 에서 BFF 패턴을 권고한다는 진술 | **SPA 가 client credentials 를 사용**하려는 경우(§2.1 조건)에 BFF 권고. public client + PKCE 만이면 SPA Direct 도 표준 허용 — "browser-based app 전반 의무" 아님 | `raw/official-docs/oauth-v2-1-draft-ietf.md#OA21-C4` | `official-standard` | `OA21-C4`의 조건은 source-grounded이나, 본 프로젝트는 confidential BFF client와 client-secret runtime을 아직 구현하지 않았다. 실제 BFF 선택·동작 evidence는 `documented-only`다 | ## 구현 가이드 > 본 branch 는 `documented-only` 비교/학습 branch — 실행 코드가 아니라 **BFF 대안의 사전 명세 + 결정 기준 매트릭스의 근거 매핑**이 산출물이다. 아래 in-scope 항목은 D1·D4·D5 결정의 도출이며, 소스가 원칙만 권고하고 detail 을 사용자가 정해야 하는 cell 은 `UNSUPPORTED_IMPL_DECISION` 라벨을 단다. 실 구현(코드) 등급은 모두 `planned`. ### 1. 토큰 holder 명세 > **Trace**: D4 (Curity BFF motivation — `CURITY-BFF-C1`/`C3`/`C4`) + D5 (OAuth 2.1 조건부 권고 — `OA21-C4`). BFF 의 핵심은 토큰이 흐르는 경계와 holder 를 SPA Direct 대비 이동시키는 것. > > - **UNSUPPORTED_IMPL_DECISION**: session store 백엔드(in-memory vs Redis)는 소스 미권고 — scale-out 요구에 따른 사용자 결정. trade-off: 학습 문서라 단일 인스턴스 in-memory 가정으로 충분, HA 필요 시 Redis 로 승격. | 경계 | 무엇을 보유 | 메커니즘 | 근거 | 등급 | |---|---|---|---|---| | 브라우저 (SPA) | 토큰 0개, httpOnly session cookie(JSESSIONID 등)만 | BFF 가 발급한 session cookie 로 세션 식별 | `CURITY-BFF-C4` (OAuth Agent 가 httpOnly session cookie 발급) | `documented-only` | | BFF (백엔드) | access/refresh token + session↔token mapping | server-side authorization code flow 수행 후 서버 메모리/store 에 보관 | `CURITY-BFF-C3` (모든 통신이 backend OAuth Agent 경유, token 은 SPA 미도달) | `documented-only` | | BFF ↔ Keycloak | — (server-to-server) | server-side `authorization_code` flow, access/refresh 서버 보유 | `OA21-C4` (client credentials 시 BFF 권고) | `documented-only` | | BFF ↔ Resource API | BFF 가 보유 token 을 Bearer 로 fan-out | `OAuth2AuthorizedClient` 의 access token 을 downstream 호출에 첨부 | `UNSUPPORTED_IMPL_DECISION` — Curity "OAuth Agent" 의 Spring 대응이 `OAuth2AuthorizedClient` 인지 미확정(Claims To Verify #4). trade-off: Spring 표준 API 로 가정, vendor 1:1 대응은 미검증 | `planned` | ### 2. Spring Security `oauth2Login` (BFF) 구성 사전 명세 > **Trace**: D5 (`OA21-C4` — BFF 권고) + D4 (`CURITY-BFF-C4` — session cookie 모델). BFF 를 Spring 으로 구현하면 SPA Direct 의 `oauth2-resource-server` 대신 `oauth2-client` starter 를 쓴다는 것이 명확한 분기점(진행 중 메모). > > - **UNSUPPORTED_IMPL_DECISION**: `client-id: bff-client`·`scope`·`redirect-uri` 의 구체 값은 소스가 아니라 배포 환경이 정함. trade-off: 본 명세는 형태(shape)만 확정, 값은 실 realm 등록 시점에 채움. | 항목 | 명세 | 근거 | 등급 | |---|---|---|---| | 의존성 | `spring-boot-starter-oauth2-client` (SPA Direct 의 `-resource-server` 와 대비되는 분기점) | 진행 중 메모 + D5 | `documented-only` | | flow wiring | `http.oauth2Login(...)` + `http.sessionManagement(...)` — 백엔드가 authorization code flow 자동 수행 + session 생성 + `OAuth2AuthorizedClient` 에 토큰 보관 | D5 (`OA21-C4`) | `documented-only` | | 브라우저 세션 | `oauth2Login` 이 인증 후 httpOnly session cookie 발급 (Curity 의 OAuth Agent 역할과 동등) | `CURITY-BFF-C4` | `documented-only` | | `application.yml` | `registration.keycloak` (client-id/secret/authorization_code/redirect-uri) + `provider.keycloak.issuer-uri` — 값은 `UNSUPPORTED_IMPL_DECISION` | D5 | `planned` | | 실 동작 검증 | Boot 3.x 에서 `/login/oauth2/code/keycloak` callback 200 + session cookie 발급 확인 | Claims To Verify #1 | `planned` | ### 3. SPA Direct vs BFF 결정 기준 매트릭스 — 셀별 근거 매핑 > **Trace**: D1 (SPA Direct 채택) + D5 (조건부 BFF 권고). 본 매트릭스가 "언제 어느 패턴" 선택 조건의 근거. §TODO 의 매트릭스(원본 표) 각 셀을 supporting claim 또는 `UNSUPPORTED_IMPL_DECISION` 으로 분해 — Claims To Verify #5(셀→claim 매핑 `planned`)를 종결. > > - **UNSUPPORTED_IMPL_DECISION**: "백엔드 stateless"·"운영 단순성"·"다중 microservice"·"revocation 즉시성" 셀은 본 branch Sources 에 직접 인용이 없는 **아키텍처 분석 통찰**이다. trade-off: 일반 원리(BFF=stateful session store / JWT=stateless revocation 난이도)로 성립하나 official 단정 불가 — 형제 branch 결정에 위임(§엣지·실패·의존). | 매트릭스 셀 | 우위 | 뒷받침 근거 | 판정 | |---|---|---|---| | XSS 민감도 (금융/의료) | BFF | `CURITY-BFF-C1` (token 브라우저 밖 = XSS 보호), `CURITY-BFF-C2` (SPA 악성코드가 token read 가능), `CURITY-BFF-C6` (refresh token 탈취 위험) | company-case-study | | 모바일/네이티브 동일 흐름 | SPA Direct | `RFC8252-C1` (native 는 자체 public-client PKCE 흐름 — SPA Direct token 흐름 재사용 가능, BFF session cookie 는 부적합) | official-standard (부분) | | 백엔드 stateless 유지 | SPA Direct | `UNSUPPORTED_IMPL_DECISION` — BFF 는 session store 필요(stateful)라는 일반 원리. `CURITY-BFF-C4` 의 "session cookie 발급"이 stateful 함의를 뒷받침하나 직접 단정은 아님 | 분석 통찰 | | 운영 단순성 (session store 불필요) | SPA Direct | `UNSUPPORTED_IMPL_DECISION` — 위와 동일(session store 유무) | 분석 통찰 | | 토큰 revocation 즉시성 | BFF (session 종료) | `UNSUPPORTED_IMPL_DECISION` — JWT stateless = revocation 난이도는 형제 [[raw/branch-notes/feature-keycloak-refresh-token-rotation]] 소관. 본 branch 직접 인용 없음 | 위임 | | OAuth 2.1 draft 권고 | BFF (조건부) | `OA21-C4` (client credentials 사용 시 BFF 권고 — 무조건 아님) | official-standard | | 다중 backend microservice | SPA Direct (각자 JWT 검증) | `UNSUPPORTED_IMPL_DECISION` — 각 RS 의 aud 검증은 형제 [[raw/branch-notes/feature-keycloak-spring-rs-audience-validator]] 소관. 본 branch 직접 인용 없음 | 위임 | ## 엣지·실패·의존 > R4(깊이 게이트) 캡처용. BFF 는 SPA Direct 대비 stateful 로 전환되므로 정상 경로 밖의 실패/엣지가 늘어난다. 본 branch 는 `documented-only` 이나, 실 구현 시 부딪힐 실패 경로와 형제 계약 의존을 미리 열거한다. - **실패·엣지 경로**: - **session store 장애 → 전체 로그인 무효화**: BFF 는 session↔token mapping 을 보유(§구현 가이드 §1)하므로 store 장애 시 모든 활성 세션 유실. 기대 동작: 외부 session store(Redis) HA 또는 sticky session. 근거: `CURITY-BFF-C4` 의 session cookie 모델(D4). - **CSRF surface 증가**: session cookie 는 브라우저가 자동 첨부 → CSRF 취약. 기대 동작: CSRF token(동기화 토큰) 또는 `SameSite=Lax/Strict` cookie 속성 필수. `CURITY-BFF-C4` 는 "session cookie 발급"만 보장하고 CSRF 통제는 미언급 — 별도 명시 필요. - **scale-out 시 session sharing**: 다중 BFF 인스턴스면 session 공유(Redis) 필수. sticky session 은 인스턴스 장애 시 해당 세션 유실. `UNSUPPORTED_IMPL_DECISION`(§구현 가이드 §3 "백엔드 stateless" 셀과 동일 원리). - **모바일 클라이언트 handoff**: BFF session cookie 모델은 native 앱에 부적합. native 는 `RFC8252-C1` 의 public-client PKCE 별도 흐름이 MUST(D3). BFF 로 모바일까지 커버하려면 추가 endpoint 설계 필요. - **Keycloak 미가용**: BFF 는 server-side authorization code flow 로 token 을 획득하므로 로그인 시점 Keycloak 장애 → 신규 로그인 차단(기존 세션은 BFF 보유 token 만료 전까지 유지). SPA Direct 와 달리 브라우저가 직접 Keycloak 을 치지 않음. - **다른 계약 의존**: - [[raw/branch-notes/feature-keycloak-internal-spa-direct-no-google]] (P2A parent) 의 `D1`(SPA Direct = 브라우저 token 보유 정의) — 본 비교의 SPA Direct 기준선. 그 정의가 바뀌면 본 매트릭스 전체가 영향. - [[raw/branch-notes/feature-keycloak-spa-token-storage-tradeoff]] D1 — pure SPA는 access/refresh 모두 memory-only이며 reload 시 재인증한다. HttpOnly refresh cookie는 TMB/BFF variant일 때만 평가하며, 본 매트릭스의 SPA Direct 기준선은 owner 결론을 consume한다. - [[raw/branch-notes/feature-keycloak-refresh-token-rotation]] 의 `D1`(rotation 활성화 — reuse detection) + `D2`(access token revocation = 짧은 TTL 로 해결) — 매트릭스 "revocation 즉시성" 셀(§구현 가이드 §3, `UNSUPPORTED_IMPL_DECISION` 위임)이 이 결정에 의존. 그 branch 가 rotation 정책을 바꾸면 SPA Direct 의 revocation 약점 평가가 달라짐. - [[raw/branch-notes/feature-keycloak-spring-rs-audience-validator]] 의 `D1`(iss+sig+exp+aud 4종 검증, aud 는 custom validator 필수) + `D4`(SPA client scope 에 Audience mapper 등록 필수) — 매트릭스 "다중 microservice" 셀(위임)이 각 RS 의 aud 검증에 의존. fan-out 시 각 downstream 이 자기 client 를 aud 로 검증해야 cross-client reuse 방지. - [[raw/branch-notes/feature-keycloak-edge-forwardauth-no-google]] (P1A) — BFF 와 개념 혼동 방지(진행 중 메모). P1A = 별도 reverse proxy 가 인증 검문소, BFF = application backend 자체가 OAuth client. 계약 의존은 아니나 경계 구분 유지 필요. ## 검증해야 할 주장 > 공식 문서 / 사례는 근거지만, 본 프로젝트의 실제 동작은 자동 보장되지 않는다. 구현 전후 검증 항목. | Claim | Why uncertain | How to verify | Status | |---|---|---|---| | Spring Security `oauth2Login` (`spring-boot-starter-oauth2-client`) 가 본 branch 본문 yml 설정 그대로 Keycloak 과 authorization code flow 를 성공시키는지 | 본 branch 의 yml 은 `documented-only` 단계 — 실 구현 없음. starter 버전 / Spring Boot 3.x compat 확인 안 됨 | 실제 Spring Boot 3.x project 에 의존성 추가 + `application.yml` 적용 후 `/login/oauth2/code/keycloak` callback 200 확인, session cookie 발급 확인 | `planned` | | Keycloak 의 client 설정 (Standard Flow + Public + PKCE S256) 이 P2A SPA Direct 와 정합한지 | 본 branch 는 BFF 비교만 다루고 P2A client 설정의 실 등록을 안 했음 | Keycloak realm export → client config JSON 에서 `standardFlowEnabled=true`, `publicClient=true`, `attributes.pkce.code.challenge.method=S256` 확인 | `needs-confirmation` | | BFF 가 모바일 클라이언트에서 실제로 동작 불가한지 (또는 별도 흐름이 정확히 필요한지) | 본 branch Sources 에 직접 인용 없음 — 본문 통찰만 | RFC 8252 (OAuth 2.0 for Native Apps) 정독 + `raw/official-docs/security-oauth2-pkce-rfc-8252.md` 와 cross-check, 모바일 SDK 에서 BFF session cookie 핸들링 동작 확인 | `needs-confirmation` | | Curity 의 "OAuth Agent" 명명이 다른 vendor (Auth0, IdentityServer, Spring Authorization Server) 의 BFF 구현에도 1:1 대응되는지 | `CURITY-BFF-C3` 의 "OAuth Agent" 는 vendor-specific 명명 | 각 vendor 의 BFF docs 정독 — Spring Security `oauth2Login` 의 `OAuth2AuthorizedClient` 가 동등 역할인지 확인 | `planned` | | 결정 기준 매트릭스 (XSS 민감도 / 모바일 / stateless / 운영 / revocation / OAuth 2.1 권고 / multi-microservice) 의 각 셀이 본 sources 중 어느 인용으로 직접 뒷받침되는지 | 본 branch 본문 매트릭스는 종합 판단 — 셀별 source mapping 부재 | 각 셀마다 supporting claim 명시 또는 UNSUPPORTED 표시로 분해 | `planned` | ## 마주친 문제 - 이슈 1: P1A(Edge ForwardAuth)와 BFF의 차이를 한 문장으로 설명하기 까다로움. - 원인: 둘 다 "토큰을 브라우저에서 분리"하지만 분리 주체와 위치가 다름 - 시도: (문서 정리) - 해결: P1A = 별도 reverse proxy가 인증 / BFF = application backend 자체가 OAuth client — `documented-only` ## 묶음 - [[raw/company-tech-blogs/curity-bff-pattern-spa]] - [[raw/official-docs/keycloak-securing-apps-overview-official]] - [[raw/official-docs/oauth-v2-1-draft-ietf]] - [[raw/official-docs/owasp-html5-storage-xss-spa]] > 본 sub-sub-branch 는 leaf — 자식 자료 없음. Phase 3 P3A 실 구현 또는 외부 산출물 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화. ### 오류 기록 (이 sub-sub-branch 작업 중 발생) - (없음 — 현재 documented-only 단계) ### 면접 준비 (이 작업에서 나올 수 있는 면접 질문) - (없음 — Phase 3 실 구현 단계에 누적) ## 관련 일일 노트 ## 완료 후 정리 > 머지/종료 시점에 채움. `/ingest`가 이 섹션을 기준으로 wiki/projects/에 추출. - PR 링크: (미구현 — 문서까지만) - 리뷰 메모: - 머지 결과 / 배포 환경: 없음 (P2A는 `documented-only` 범위) - **wiki 추출 대상**: 현 단계 없음. 6 패턴 + BFF 매트릭스 완성 후 `wiki/concepts/bff-vs-spa-direct.md` 합성 후보. - **추출하지 않을 항목**: BFF 자체 구현 없음. SPA Direct도 P2A 구현 없음. `documented-only` 유지.