325 lines
28 KiB
Markdown
325 lines
28 KiB
Markdown
---
|
|
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`
|
|
|
|
<!-- section-id: branch-parent -->
|
|
## 부모 (필수)
|
|
|
|
[[raw/branch-notes/feature-keycloak-patterns]]
|
|
|
|
<!-- GENERATED: branch-contract:start -->
|
|
<!-- section-id: branch-contract-packet -->
|
|
## 브랜치 계약 패킷
|
|
|
|
- **생성 시 프로젝트 개정**: `1`
|
|
- **패킷 스키마**: `contract_packet: 1`
|
|
- **완료 조건**: project governance hub가 AP1~AP4 taxonomy와 child progress index를 유지한다
|
|
|
|
<!-- section-id: inherited-project-decisions -->
|
|
### 상속한 프로젝트 결정
|
|
|
|
| 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]] |
|
|
|
|
<!-- section-id: branch-local-decisions -->
|
|
### 브랜치 지역 결정
|
|
|
|
| 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` |
|
|
|
|
<!-- section-id: declared-overrides -->
|
|
### 선언한 예외
|
|
|
|
| Override ID | Overrides | Reason | Approval | Status |
|
|
|---|---|---|---|---|
|
|
|
|
없음.
|
|
<!-- GENERATED: branch-contract:end -->
|
|
|
|
<!-- section-id: branch-goal -->
|
|
## 목표
|
|
|
|
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: (구현 없음)
|
|
|
|
<!-- section-id: branch-scope -->
|
|
## 범위
|
|
|
|
### 포함 범위
|
|
|
|
- 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: <secret>
|
|
authorization-grant-type: authorization_code
|
|
redirect-uri: "{baseUrl}/login/oauth2/code/keycloak"
|
|
scope: openid, profile, email
|
|
provider:
|
|
keycloak:
|
|
issuer-uri: https://<keycloak>/realms/<realm>
|
|
```
|
|
- `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`
|
|
|
|
## 묶음
|
|
|
|
<!-- GENERATED: sources:start -->
|
|
- [[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]]
|
|
<!-- GENERATED: sources:end -->
|
|
|
|
> 본 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` 유지.
|