Files
llm-wiki/raw/branch-notes/feature-keycloak-edge-forwardauth-no-google.md

373 lines
30 KiB
Markdown

---
title: branch / feature-keycloak-edge-forwardauth-no-google (P1A Edge Forward Auth, no Google)
source_type: branch-note
status: raw
id: BR-KEYCLOAK-CHILD-12F5B5DA
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-edge-forwardauth-no-google
parent_branch: feature-keycloak-patterns
related_projects: [keycloak-patterns]
tags: [branch, keycloak-patterns, auth, oauth2, oidc]
created: 2026-05-25
target_merge:
status_label: in-progress
contract_packet_sha256: e8d7d4e1b0766c23adb5879a98225689106a683f5882dd49a3b4b24b5fc18f08
---
# branch: feature-keycloak-edge-forwardauth-no-google (P1A Edge Forward Auth, no Google)
> Layer: `raw/branch-notes/` — Keycloak 패턴 P1A 한정 sub-branch. **Ingress(nginx `auth_request` 또는 Traefik `forwardAuth`)가 외부 auth service(oauth2-proxy baseline)에 인증을 위임**하고 백엔드는 인증 코드를 갖지 않는 패턴. Google federation 없음(=P1B는 별도 sub-branch).
> 본 sub-branch는 **문서까지만**(=`documented-only`). 실제 ingress/Traefik 환경 구축은 root branch의 P3A 한정.
> `status_label`: `in-progress`
<!-- section-id: branch-parent -->
## 부모 (필수)
- **Parent branch (root)**: [[raw/branch-notes/feature-keycloak-patterns]]
- **Parent project**: [[raw/project-notes/keycloak-patterns-overview]]
- **Sibling sub-branches**:
- [[raw/branch-notes/feature-keycloak-edge-forwardauth-google-federation]] — P1B (Edge + Google)
- [[raw/branch-notes/feature-keycloak-internal-spa-direct-no-google]] — P2A (Cluster-internal, no Google)
- [[raw/branch-notes/feature-keycloak-internal-spa-direct-google-federation]] — P2B (Cluster-internal + Google)
- [[raw/branch-notes/feature-keycloak-single-ec2-no-google]] — P3A (Single EC2, no Google)
- [[raw/branch-notes/feature-keycloak-single-ec2-google-federation]] — P3B (Single EC2 + Google)
<!-- 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 변형이다 | no-Google Edge ForwardAuth를 AP4 비교 자료로 분류한다 | [[raw/project-notes/keycloak-patterns-overview]] |
<!-- section-id: branch-local-decisions -->
### 브랜치 지역 결정
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|---|---|---|---|
| D1 | ingress 축과 auth-service 축을 분리한다 | `local` | [[raw/project-notes/keycloak-patterns-overview]] | `proposed` |
| D2 | upstream identity header naming을 고정한다 | `local` | [[raw/project-notes/keycloak-patterns-overview]] | `proposed` |
| D3 | backend authentication을 edge ForwardAuth에 위임한다 | `local` | [[raw/project-notes/keycloak-patterns-overview]] | `proposed` |
| D4 | ingress-only traffic으로 header spoofing을 방어한다 | `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 -->
## 목표
Edge forward-auth 패턴이 무엇이고, 왜 이 배치를 택하는지를 컴포넌트 다이어그램 + 토큰 교환 sequence + 신뢰 경계 수준으로 정리. 백엔드 코드에서 인증 로직을 제거하고 **edge proxy 단일 지점에서 zero-trust ingress** 를 강제하는 흐름을 면접에서 설명할 수 있어야 함.
핵심 질문 두 개에 답할 수 있어야 한다:
1. 왜 백엔드에 JWT validator를 두지 않고 edge proxy에 인증을 위임하는가? → 다중 서비스에 일관 인증 + 인증 코드 0줄.
2. edge proxy를 신뢰하는 대신 잃는 것은? → 백엔드는 헤더만 보고 사용자를 식별하므로, ingress 우회 경로가 있으면 헤더 spoofing 위험.
- 이슈:
- PR:
<!-- section-id: branch-scope -->
## 범위
### 포함 범위
- P1A 컴포넌트 다이어그램 (텍스트 + Mermaid)
- 토큰 교환 sequence 7단계
- Ingress 선택(nginx vs Traefik)과 auth service 선택(oauth2-proxy vs 호환 OIDC agent)의 2축 비교
- nginx `auth_request` 방식과 Traefik `forwardAuth` 방식의 차이
- 신뢰 경계 정의 (ingress → proxy까지)
- 장단점 / 운영 비용 / 보안 surface
- 외부 공식 문서 raw 보존 (oauth2-proxy, Traefik, nginx)
### 제외 범위
- Google IdP brokering (=P1B sub-branch에서 다룸)
- 실제 K8s / docker-compose 환경 구축 (root branch P3A 한정)
- BFF 패턴 (별도 결합 패턴, sub-branch에서 언급만)
- mTLS / FAPI / DPoP 등 고급 보안 옵션
- oauth2-proxy 비-Keycloak provider (GitHub, Google direct 등)
## 근거 (필수, 최소 1개+)
> 본 sub-branch의 P1A Edge ForwardAuth 패턴 채택 근거. 상세 대안 비교는 §외부 근거 / 대안 조사 참조.
| Source | 정당화하는 결정 |
|---|---|
| [[raw/official-docs/oauth2-proxy-overview-config-official]] | oauth2-proxy 공식 — 채택 컴포넌트 근거 (load-bearing: `OAUTH2PROXY-C2`/`C3`; `C1``needs-confirmation`, 결정 미인용) |
| [[raw/official-docs/oauth2-proxy-keycloak-oidc-provider-official]] | oauth2-proxy ↔ Keycloak OIDC 연동 — `provider=keycloak-oidc` 채택 근거 |
| [[raw/official-docs/oauth2-proxy-nginx-integration-official]] | nginx auth_request 통합 — Ingress-Nginx 결합 근거 |
| [[raw/official-docs/nginx-auth-request-module-official]] | nginx ngx_http_auth_request_module — subrequest 동작 근거 |
| [[raw/official-docs/traefik-forwardauth-middleware-official]] | Traefik ForwardAuth middleware — K8s 환경 대안 비교 근거 |
## 컴포넌트 다이어그램
### 텍스트
```
Browser → Ingress (Nginx / Traefik)
├── (1) ForwardAuth subrequest → oauth2-proxy / compatible auth service
│ │
│ └── (2) OIDC handshake → Keycloak
│ │
│ ← (3) 세션 쿠키 + X-Auth-Request-* 헤더 ←┘
↓ (4) 인증 통과 시 backend로 forward (헤더만 신뢰)
Backend (인증 코드 0줄, 헤더 trust만)
```
### Mermaid
```mermaid
flowchart LR
B[Browser] -->|HTTPS| I[Ingress: Nginx or Traefik]
I -. auth_request or forwardAuth .-> P[oauth2-proxy / auth service]
P -. OIDC .-> K[Keycloak]
P -- 202 + X-Auth-Request-* --> I
I -->|trusted headers| BE[Backend API]
```
## 토큰 교환 sequence
> "Browser, Ingress, oauth2-proxy, Keycloak, Backend" 5개 액터 기준.
1. **Browser → Ingress**: unauthenticated request `GET /api/orders` (쿠키 없음).
2. **Ingress → oauth2-proxy `/oauth2/auth`**: nginx의 `auth_request` 디렉티브 또는 Traefik의 `forwardAuth` 미들웨어가 subrequest 전송. 이 endpoint는 **요청을 프록시하지 않고** 202(Accepted) 또는 401(Unauthorized)만 반환.
3. **oauth2-proxy → Keycloak `/protocol/openid-connect/auth`**: 쿠키 없으므로 401 → Ingress가 error_page로 받아 named location `@oauth2_signin`으로 302 redirect 발급. 사용자 브라우저가 Keycloak 로그인 페이지로 이동.
4. **Browser → Keycloak 로그인 UI → 사용자 인증 → callback**: Authorization Code Flow + PKCE. Keycloak이 oauth2-proxy의 callback URL (`/oauth2/callback`)로 `code` 파라미터와 함께 redirect.
5. **oauth2-proxy → Keycloak `/protocol/openid-connect/token`**: `code` + `client_secret``access_token` + `id_token` + (옵션) `refresh_token` 교환. oauth2-proxy는 confidential client.
6. **oauth2-proxy → 세션 쿠키 발급**: JavaScript가 raw token을 읽지 못하는 HttpOnly 세션 쿠키(`_oauth2_proxy`)를 발급한다. cookie-backed store면 encrypted cookie가 token material을 보유할 수 있고, Redis/server-side store면 cookie는 opaque session identifier만 보유한다. 이후 `auth_request` subrequest 통과 시 `X-Auth-Request-User`, `X-Auth-Request-Email`, `X-Auth-Request-Groups` 헤더를 Ingress에 응답한다(P1A baseline은 access-token upstream 전달 미사용).
7. **Ingress → Backend**: Ingress가 응답 헤더에서 `auth_request_set` 으로 변수 추출 → `proxy_set_header X-User $user; X-Email $email;` 형식으로 backend에 헤더 주입. **백엔드는 JWT 검증을 하지 않고 헤더만 신뢰**.
## 장점 / 단점
### 장점
- **백엔드 인증 코드 0줄**: Resource Server 보일러플레이트(spring-security-oauth2-resource-server, JWT decoder, JWKS cache 등) 불필요.
- **다중 서비스 일관 인증**: 같은 ingress 뒤의 모든 backend에 동일한 인증 정책 적용. 마이크로서비스 환경에서 인증 코드 분산을 방지.
- **raw token의 JavaScript 노출 없음**: HttpOnly cookie라 SPA script가 access/refresh token bytes를 직접 읽지 못한다. 다만 cookie-backed session이면 브라우저가 encrypted token-bearing cookie를 보유하므로 server-side custody와 동일하다고 표현하지 않는다.
- **운영 일원화**: 인증 정책 변경(allowed-role, allowed-group 등) 시 oauth2-proxy config만 수정.
### 단점
- **헤더 spoofing risk**: 백엔드가 ingress-only traffic을 강제하지 못하면(예: backend가 직접 NodePort 노출), 공격자가 `X-Auth-Request-User: admin` 헤더를 위조해 우회 가능.
- **proxy SPOF**: oauth2-proxy 다운 시 모든 backend 접근 불가. HA 구성 필수.
- **세션 저장 방식별 위험**: cookie-backed store가 access token까지 encrypted cookie에 담으면 nginx의 기본 4kb 헤더 한도를 넘어 split-cookie 처리가 필요하다. Redis/server-side store면 cookie는 opaque ID지만 외부 state store 운영 책임이 생긴다.
- **백엔드가 토큰 claim 직접 접근 불가**: scope / custom claim 기반 fine-grained 권한 체크가 필요하면 추가로 `X-Auth-Request-Access-Token` 헤더로 토큰 자체를 전달하거나, 결국 backend에서도 JWT 파싱해야 함.
## 신뢰 경계
```
[ Public Internet ] ←→ [ Ingress + Forward-Auth Proxy ] ←→ [ Backend ]
untrusted ← 인증 경계 (boundary) trusted-by-header
```
- **인증 boundary**: ingress → proxy 까지. 이 구간에서 사용자 식별 확정.
- **백엔드 전제**: ingress 외 경로로는 도달 불가. 구체적 강제 수단:
- K8s: `NetworkPolicy` 로 ingress namespace에서만 backend pod 접근 허용.
- VM: backend listen address를 loopback / private subnet으로 한정. Security Group으로 ingress IP만 허용.
- **이 전제가 깨지면** 패턴 전체가 깨짐 → 외부 공격자가 backend에 직접 `X-Auth-Request-User: anyuser` 헤더로 요청 가능.
## 제외 범위 (재확인)
- Google federation은 **P1B 별도 sub-branch** ([[raw/branch-notes/feature-keycloak-edge-forwardauth-google-federation]]). 본 sub-branch는 Keycloak 자체 user store만 사용.
## TODO
각 항목 옆에 증거 등급 표기: `actually-implemented` | `locally-verified` | `prod-verified` | `documented-only` | `planned` | `needs-confirmation`
- [x] P1A 컴포넌트 다이어그램 (텍스트 + Mermaid) — 등급: `documented-only`
- [x] 토큰 교환 sequence 7단계 — 등급: `documented-only`
- [x] Ingress(nginx/Traefik)와 auth service(oauth2-proxy/호환 agent) 선택축 분리 (D1) — 등급: `documented-only`
- [x] 신뢰 경계 정의 + header spoofing 방어(D4)를 자식 branch 로 위임 — 등급: `documented-only`
- [ ] 실 구현(oauth2-proxy config + nginx `auth_request` block) — root branch **P3A** 한정 — 등급: `planned`
- [ ] §Claims To Verify 5개 주장 실측 (nginx build flag / 4kb cookie / 헤더 전파 / ingress-only / Traefik 비-2XX) — 등급: `planned` (일부 `needs-confirmation`)
## 진행 중 메모
- **P1A 의 본질 한 줄**: edge proxy 가 인증을 종결하고 backend 는 헤더만 신뢰 → "backend 인증 코드 0줄" 이 최대 이점이자 동시에 최대 약점(header spoofing). 이 한 문장이 §장점·§단점·D3·D4 를 관통한다.
- **subrequest mode endpoint 구분**: oauth2-proxy 의 `/oauth2/auth` 는 요청을 프록시하지 않고 2xx/401 만 반환하는 subrequest 전용 endpoint(`O2PN-C2`)로, 정상 reverse-proxy mode(`/oauth2/start`,`/oauth2/callback`)와 경로가 다르다. nginx `auth_request` 는 이 endpoint 만 부른다 — 두 mode 를 혼동하면 302 루프가 난다.
- **4kb cookie 함정**: cookie-backed store가 access token을 encrypted cookie에 실으면 nginx 기본 헤더 한도(4kb)를 넘어 split cookie가 되고, nginx가 첫 `Set-Cookie`만 복사하는 문제(`O2PN-C6`)가 있다. Redis/server-side store에서는 이 크기 위험 대신 state-store 운영 위험을 검증한다.
- 본 sub-branch 는 `documented-only`. `wiki/projects/` 승급은 root 의 6-패턴 비교 매트릭스 시점에 일괄 처리(개별 승급 없음).
## 결정 사항 (decisions)
- **D1** 2026-05-25: 선택을 두 축으로 분리한다.
- **Ingress 축**: Ingress-Nginx면 `auth_request`, Traefik이면 `forwardAuth` middleware를 사용한다.
- **Auth service 축**: oauth2-proxy를 baseline OIDC agent로 두며, 다른 호환 auth service를 쓰려면 동일한 allow/deny·header contract를 검증한다.
- Traefik `forwardAuth`는 OIDC session provider 자체가 아니라 외부 auth service를 호출하는 middleware다. 따라서 `Traefik + oauth2-proxy`는 정상 조합이며 상호 배타적 대안이 아니다.
- **D2** 2026-05-25: 헤더 이름은 nginx 측 `X-Auth-Request-User` 가 사실상 표준 (oauth2-proxy 응답 헤더). Traefik의 `X-Forwarded-User`는 oauth2-proxy 측 옵션 `--pass-user-headers`가 추가 발급하는 헤더로, 본 문서에서는 nginx 계열 명명 우선.
- **D3** 2026-05-25: 백엔드 인증 코드를 제거하고 edge proxy 의 ForwardAuth subrequest 로 인증 위임 (Edge ForwardAuth 패턴 채택).
- **D4** 2026-05-25: header spoofing 방어를 위해 ingress-only traffic 강제 (K8s NetworkPolicy 또는 VPC SG) — backend 가 ingress 외 경로로 도달 불가해야 함.
## 결정-근거 매핑
> 본 sub-branch 의 P1A Edge ForwardAuth 패턴 채택 결정과 raw source claim 매핑. claim 형식 `raw/<category>/<slug>.md#<CLAIM-ID>`.
| Decision ID | Decision | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|
| D1 | Ingress 축(nginx `auth_request` / Traefik `forwardAuth`)과 auth service 축(oauth2-proxy / compatible OIDC agent)을 분리. baseline은 두 ingress 모두 oauth2-proxy 호출 | `raw/official-docs/oauth2-proxy-overview-config-official.md#OAUTH2PROXY-C2`, `raw/official-docs/oauth2-proxy-overview-config-official.md#OAUTH2PROXY-C3`, `raw/official-docs/oauth2-proxy-keycloak-oidc-provider-official.md#O2PK-C1`, `raw/official-docs/oauth2-proxy-keycloak-oidc-provider-official.md#O2PK-C2`, `raw/official-docs/oauth2-proxy-nginx-integration-official.md#O2PN-C1`, `raw/official-docs/traefik-forwardauth-middleware-official.md#TFA-C1`, `raw/official-docs/traefik-forwardauth-middleware-official.md#TFA-C3`, `raw/official-docs/traefik-forwardauth-middleware-official.md#TFA-C4` | `official-vendor-doc` (3 vendors: oauth2-proxy + Keycloak + Traefik) | ingress별 비-2XX 처리 차이와 auth service 대체 호환성은 실측 필요 |
| D2 | 헤더 명명은 nginx 계열 `X-Auth-Request-User` (oauth2-proxy 응답 헤더) 우선 | `raw/official-docs/oauth2-proxy-overview-config-official.md#OAUTH2PROXY-C3`, `raw/official-docs/oauth2-proxy-overview-config-official.md#OAUTH2PROXY-C4`, `raw/official-docs/oauth2-proxy-nginx-integration-official.md#O2PN-C3` | `official-vendor-doc` | `X-Auth-Request-*` (subrequest mode response headers) vs `X-Forwarded-*` (`--pass-user-headers` upstream forwarding) 의 정확한 default 활성화 여부는 OAUTH2PROXY-C4 의 "Does not prove" 에 명시 — 별도 config 확인 필요 |
| D3 | 백엔드 인증 코드 제거 + edge proxy 의 ForwardAuth subrequest 로 인증 위임 (Edge ForwardAuth 패턴 채택) | `raw/official-docs/nginx-auth-request-module-official.md#NGAR-C1`, `raw/official-docs/nginx-auth-request-module-official.md#NGAR-C2`, `raw/official-docs/nginx-auth-request-module-official.md#NGAR-C4`, `raw/official-docs/nginx-auth-request-module-official.md#NGAR-C5`, `raw/official-docs/oauth2-proxy-nginx-integration-official.md#O2PN-C2`, `raw/official-docs/oauth2-proxy-nginx-integration-official.md#O2PN-C5`, `raw/official-docs/traefik-forwardauth-middleware-official.md#TFA-C1` | `official-standard (nginx) + official-vendor-doc (oauth2-proxy, Traefik)` | "다중 백엔드에 일관 인증" / "백엔드 코드 0줄" 의 운영상 이점은 본 raw claim 들이 직접 enumerate 하지 않음 — 운영 관행 추론 |
| D4 | header spoofing 방어 — ingress-only traffic 강제 (NetworkPolicy / VPC SG) | UNSUPPORTED_DECISION (오current Sources 표 에는 NetworkPolicy / VPC SG enforcement 의 공식 raw 가 없음 — sub-sub-branch `feature-keycloak-header-spoofing-defense` 에서 별도 raw 보존 필요) | `internal-reasoning` (보안 일반 원칙) | NetworkPolicy default deny / VPC SG 정확한 구성 패턴이 본 sub-branch 의 Sources 표에 부재. 구현 단계 진입 전 K8s NetworkPolicy 공식 doc + AWS SG 공식 doc 을 raw 로 보존 필요 |
## 구현 가이드
> 본 sub-branch 는 `documented-only` — 산출물은 코드가 아니라 패턴 문서다. 아래는 root branch **P3A** 에서 실제 구현 시 이 branch 의 결정(D1~D4)이 강제하는 config 앵커의 사전 명세. 모든 항목 등급 `planned`(코드 미존재 — `src/` grep 으로 확정 안 됨).
> **`NO_GROUND_TRUTH`**: 본 branch 는 `keycloak-patterns` 학습 프로젝트 소속으로 ca-tmpl skeleton 범위 밖 인프라 설정이다 — error-codes/env-keys/headers registry 등 ca-tmpl 계약 SSOT 대조 대상이 아니며, 근거는 vendor 공식 doc(oauth2-proxy / nginx / Traefik)이다.
### 1. oauth2-proxy config (K8s + Ingress-Nginx 경로)
> **Trace**: D1(nginx ingress + oauth2-proxy baseline) + D2(헤더 명명 nginx 계열). Supporting: `OAUTH2PROXY-C2`, `O2PK-C1`, `O2PK-C2`, `O2PN-C3`.
>
> - **UNSUPPORTED_IMPL_DECISION**: 없음 — 아래 파라미터·값은 vendor doc 이 verbatim 명시.
| 설정 | 값 | 근거 claim |
|---|---|---|
| `--provider` | `keycloak-oidc` | `O2PK-C1` |
| `--client-id` / `--client-secret` / `--oidc-issuer-url` | confidential client 3종 필수 파라미터 | `O2PK-C1` |
| issuer URL 패턴 | Keycloak 17+ `https://<host>/realms/<realm>` (17 미만 legacy `/auth/realms/...`) | `O2PK-C2` |
| `--set-xauthrequest` | 활성 — `X-Auth-Request-User`/`-Email` 응답 헤더 발급 (subrequest mode) | `O2PN-C3` |
### 2. nginx `auth_request` location block
> **Trace**: D3(edge ForwardAuth 로 인증 위임). Supporting: `NGAR-C1`, `NGAR-C2`, `NGAR-C4`, `NGAR-C5`, `O2PN-C2`, `O2PN-C5`.
>
> - **UNSUPPORTED_IMPL_DECISION**: named location 이름(`@oauth2_signin`)은 관례적 명명 — 임의 선택 가능(vendor 예시 관용값). 강제되는 것은 이름이 아니라 "401 → 302 redirect" contract(`O2PN-C5`)뿐. trade-off: 관용값을 벗어나면 남의 예시 config 를 그대로 못 붙임.
| 디렉티브 | 역할 | 근거 claim |
|---|---|---|
| `auth_request /oauth2/auth;` | 보호 location 에서 subrequest 발사 (URI = oauth2-proxy subrequest endpoint) | `NGAR-C4`, `O2PN-C2` |
| subrequest 응답 contract | 2xx=allow, 401/403=deny | `NGAR-C2`, `O2PN-C2` |
| `auth_request_set $user $upstream_http_x_auth_request_user;` (+ `$email`) | subrequest 응답 헤더 → main request 변수 | `NGAR-C5`, `O2PN-C3` |
| `proxy_set_header X-User $user;` (+ `X-Email $email;`) | backend 로 사용자 식별 헤더 주입 | `O2PN-C3` |
| `error_page 401 = @oauth2_signin;``return 302 /oauth2/sign_in?rd=...` | 미인증 브라우저 302 redirect | `O2PN-C5` |
| nginx build | `--with-http_auth_request_module` 필수 (기본 빌드 미포함) | `NGAR-C1` · 검증 → §Claims 1 (`NGAR-C7`) |
### 3. Traefik `forwardAuth` ingress variant (auth service는 별도)
> **Trace**: D1(Traefik ingress 축). `forwardAuth.address`는 oauth2-proxy 또는 호환 auth service를 가리킨다. Supporting: `TFA-C1`, `TFA-C3`, `TFA-C4`.
>
> - **UNSUPPORTED_IMPL_DECISION**: nginx(401/403 만 deny)와 Traefik(모든 non-2XX 를 302 포함 client 에 그대로 전달)의 비-2XX 처리 contract 차이는 vendor doc 이 명시(`TFA-C1`)하나, 두 경로가 동일 로그인 UX 를 내는지는 미실측 → §Claims 5. trade-off: 두 경로를 "동등"으로 문서화하려면 이 실측이 선행.
| 미들웨어 옵션 | 역할 | 근거 claim |
|---|---|---|
| `forwardAuth.address` | oauth2-proxy `/oauth2/auth` 로 위임, 2XX=allow + 원본 요청 진행 | `TFA-C1` |
| `authResponseHeaders` | 인증 서버 응답 헤더(`X-Auth-Request-*`)를 forwarded request 로 복사 (충돌 헤더 대체) | `TFA-C3` |
| `authRequestHeaders` | 인증 서버로 전달할 request 헤더 필터 (비우면 전부 전달 — sensitive 헤더 노출 주의) | `TFA-C4` |
### 4. ingress-only 강제 (header spoofing 방어) — 자식 branch 로 위임
> **Trace**: D4(`UNSUPPORTED_DECISION`).
>
> - **R3 OUT_OF_BRANCH_SCOPE**: NetworkPolicy default-deny / VPC SG 의 구체 구성은 P1A 결정 범위 밖 — 자식 sub-sub-branch [[raw/branch-notes/feature-keycloak-header-spoofing-defense]] 가 owner. 본 branch 는 "ingress 외 경로로 backend 도달 불가를 강제해야 한다"는 원칙만 명시하고 enforcement detail 은 재진술하지 않는다(포인터만).
## 엣지·실패·의존
> R4(깊이 게이트) 캡처. 정상 경로(§토큰 교환 sequence) 외 구현 중 부딪힐 실패·엣지 + 다른 계약 의존.
- **실패·엣지 경로**:
- **proxy SPOF**: oauth2-proxy 다운 → 같은 ingress 뒤 모든 backend 접근 불가(§단점). 기대 동작: HA(replica ≥2) + readiness probe. 미구성 시 단일 장애점.
- **4kb cookie 초과 / split cookie**: access_token 을 cookie 에 실으면 nginx 헤더 한도 초과 → nginx 가 첫 `Set-Cookie` 만 복사(`O2PN-C6`). 기대 동작: cookie 분할 처리 또는 access_token 을 cookie 에 싣지 않음. → §Claims 2.
- **미인증 XHR/API 요청**: `O2PN-C5` 의 401→302 redirect 는 브라우저 전제. API client 는 302 를 따라가지 못함. 기대 동작: `Accept: application/json` 요청엔 401 유지(별도 처리 — `O2PN-C5` "Does not prove" 참조).
- **nginx build 에 auth_request 모듈 부재**: 기본 빌드 미포함(`NGAR-C7`) → `auth_request` directive 무효화. 기대 동작: 기동 시 config 오류로 조기 실패. → §Claims 1.
- **nginx vs Traefik 비-2XX contract 차이**: oauth2-proxy 가 5xx 반환 시 nginx(401/403 만 deny, 그 외 error)와 Traefik(모든 non-2XX 를 client 에 전달)의 최종 응답이 갈림(`TFA-C1`). → §Claims 5.
- **다른 계약 의존** (대상 브랜치 + 그 Decision ID 병기):
- [[raw/branch-notes/feature-keycloak-header-spoofing-defense]] `D1`(백엔드 ingress-뒤 격리 = 1차 방어) + `D3`(K8s NetworkPolicy default-deny) / `D4`(EC2 Security Group + private-subnet listen) 에 의존 — 이 계약이 없으면 본 branch `D3`(헤더 trust)의 전제가 깨져 외부에서 `X-Auth-Request-User` 위조가 가능해지고 P1A 패턴 전체가 무력화된다. 본 branch `D4` 는 원칙만 선언, enforcement detail 은 이 자식 owner.
- [[raw/branch-notes/feature-keycloak-oauth2-proxy-oidc-flow]] `D2`(oauth2-proxy `provider=keycloak-oidc` + `--client-id/-secret/-oidc-issuer-url` + Keycloak 17+ issuer URL 패턴)가 §sequence step 3~6 handshake 의 owner. 이 계약이 바뀌면 본 branch §구현 가이드 1 의 config 앵커(D1/D2)가 영향받음.
- [[raw/branch-notes/feature-keycloak-nginx-auth-request-integration]] `D2`(subrequest 2xx/401/403 contract) + `D3`(`auth_request_set``proxy_set_header` 2-step 헤더 전파) + `D5`(4kb cookie split 대응)가 본 branch §구현 가이드 2(nginx `auth_request` block)의 owner.
- **P1B 변형** [[raw/branch-notes/feature-keycloak-edge-forwardauth-google-federation]] — Google federation을 추가해도 [[raw/branch-notes/feature-keycloak-edge-forwardauth-no-google]] D3의 header-only backend trust를 그대로 consume한다.
## 검증해야 할 주장
> 본 sub-branch 는 `documented-only` 단계. 구현 진입 시 검증해야 할 주장 enumerate.
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
| nginx 빌드에 `--with-http_auth_request_module` 가 활성화되어 있음 | `NGAR-C7` 명시 — 기본 빌드에 포함되지 않음 | `nginx -V 2>&1 \| grep -o with-http_auth_request_module` | `planned` (구현 진입 시) |
| oauth2-proxy 가 큰 access_token (Keycloak refresh token 포함) 을 cookie 4kb 한도 내에서 처리 또는 split | `O2PN-C6` 명시 — 분할 cookie 시 nginx 가 첫 `Set-Cookie` 만 복사 | docker-compose 환경에서 큰 토큰 발급 후 브라우저 cookie 확인 + nginx access_log 의 Set-Cookie 헤더 검사 | `planned` |
| `--set-xauthrequest` 활성화 시 nginx `auth_request_set` 이 backend 까지 `X-User` / `X-Email` 헤더 전파 | `O2PN-C3` 가 contract 명시했으나 실제 nginx config 의 `proxy_set_header` 작성 필요 | backend 에 echo endpoint 추가 후 curl 로 헤더 확인 | `planned` |
| backend 가 ingress-only traffic 만 받음 (header spoofing 우회 차단) | D4 의 UNSUPPORTED_DECISION 와 동일 — 정책 enforcement 가 실제로 강제되는지 별도 검증 필요 | K8s: NetworkPolicy default-deny 적용 후 다른 namespace 에서 curl 시도 → 차단 확인. VM: backend listen address 가 loopback / private subnet 인지 `ss -tln` 확인 | `needs-confirmation` |
| Traefik `forwardAuth` 의 비-2XX 응답 처리가 nginx `auth_request` 와 호환 가능 | `TFA-C1` 가 "비 2XX 응답은 그대로 client 에 반환" 명시 — nginx 의 "401/403 만 deny, 그 외 error" 와 contract 차이 | 두 환경에서 oauth2-proxy 가 5xx 반환 시 client 가 받는 응답 비교 (curl -v) | `planned` |
## 마주친 문제
- 아직 없음(문서 단계).
## 묶음 (자식 sub-sub-branches)
<!-- GENERATED: sources:start -->
- [[raw/official-docs/nginx-auth-request-module-official]]
- [[raw/official-docs/oauth2-proxy-endpoints-official]]
- [[raw/official-docs/oauth2-proxy-keycloak-oidc-provider-official]]
- [[raw/official-docs/oauth2-proxy-nginx-integration-official]]
- [[raw/official-docs/oauth2-proxy-overview-config-official]]
- [[raw/official-docs/traefik-forwardauth-middleware-official]]
<!-- GENERATED: sources:end -->
- [[raw/branch-notes/feature-keycloak-oauth2-proxy-oidc-flow]] — oauth2-proxy 구성과 OIDC 흐름
- [[raw/branch-notes/feature-keycloak-nginx-auth-request-integration]] — nginx auth_request 통합 (4kb cookie 함정)
- [[raw/branch-notes/feature-keycloak-header-spoofing-defense]] — 헤더 spoofing 방어 (NetworkPolicy / SG / mTLS)
- [[raw/branch-notes/feature-keycloak-traefik-forwardauth-alternative]] — Traefik ForwardAuth 대안 비교
> Sources / 근거 자료는 본 문서 하단 "외부 근거 / 대안 조사" 섹션 참조. Errors / Interview prep / Lectures 는 현재 없음 (Phase 3 P3A 실 구현 또는 외부 산출물 단계에 누적 예정).
## 관련 일일 노트
## 관련 sub-branch
- [[raw/branch-notes/feature-keycloak-patterns]] (root)
- [[raw/branch-notes/feature-keycloak-edge-forwardauth-google-federation]] — P1B Edge + Google federation (본 패턴의 federation 변형)
## 외부 근거 / 대안 조사 (2026-05-25 — P1A Edge Forward Auth)
본 sub-branch의 **Edge ForwardAuth 패턴** 채택에 대한 외부 source 조사. 대안은 동일 목적(브라우저 인증 + 백엔드 신뢰)을 다른 방식으로 달성하는 패턴들과 비교.
- **채택 결정 (nginx `auth_request` 또는 Traefik `forwardAuth` ingress + oauth2-proxy baseline + Keycloak)**:
- [[raw/official-docs/oauth2-proxy-overview-config-official]] — oauth2-proxy 공식 (Reverse proxy + auth provider integration)
- [[raw/official-docs/oauth2-proxy-keycloak-oidc-provider-official]] — oauth2-proxy ↔ Keycloak OIDC 연동
- [[raw/official-docs/oauth2-proxy-nginx-integration-official]] — nginx auth_request 통합
- [[raw/official-docs/nginx-auth-request-module-official]] — nginx ngx_http_auth_request_module (2xx=allow / 401|403=deny contract)
- [[raw/official-docs/traefik-forwardauth-middleware-official]] — Traefik ForwardAuth middleware (K8s 환경 대안)
- **검토한 대안**:
- **대안 1: SPA Direct OIDC + Resource Server (P2A)** — 클라이언트가 직접 Keycloak 호출, 백엔드는 JWT validator. 장: 백엔드 stateless / 단: SPA에 token 노출. 비교 sub-branch: [[raw/branch-notes/feature-keycloak-internal-spa-direct-no-google]].
- **대안 2: BFF (Backend-for-Frontend)** — 백엔드 session cookie + 백엔드가 token holder. 장: XSS surface 축소 / 단: 백엔드 stateful. (Curity / Philippe De Ryck 권고).
- **대안 3: API Gateway 인증 (Kong, AWS API Gateway + Cognito)** — vendor lock-in + cloud 종속.
- **대안 4: Service Mesh (Istio AuthorizationPolicy + JWT filter)** — K8s mesh 인프라 전제.
- **대안 5: 백엔드 직접 인증 (Spring Security `oauth2Login`)** — 백엔드가 redirect/callback 처리. 단일 서비스에는 단순하나 다중 서비스 시 중복.
- **비교 핵심**: Edge ForwardAuth는 **다중 백엔드 서비스가 동일 인증을 공유**할 때 가장 단순. 백엔드 코드 0줄 인증. 단, header spoofing 방어 (ingress-only traffic 강제 — K8s NetworkPolicy 또는 VPC SG) 필수. SPA Direct는 mobile/IoT까지 같은 token으로 쓸 때 유리. BFF는 XSS 민감 환경(예: 금융). Service Mesh는 이미 mesh 도입된 환경.
## 완료 후 정리
> 본 sub-branch는 **문서까지만**. wiki 추출은 root branch의 비교 매트릭스 시점에 일괄 처리.
- PR 링크:
- 리뷰 메모:
- 머지 결과 / 배포 환경: 해당 없음 (문서 단계)
- **wiki 추출 대상** (verified만, `wiki/projects/`로만 추출):
- `actually-implemented` 항목: 없음
- `locally-verified` 항목: 없음
- `prod-verified` 항목: 없음
- **추출하지 않을 항목** (planned / documented-only / abandoned):
- 본 sub-branch 전체가 `documented-only` 등급. 추후 root branch의 6 패턴 비교 매트릭스에 인용되는 형태로만 활용. `wiki/projects/` 직접 승급 없음.