52 KiB
title, source_type, status, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, branch, parent_branch, related_projects, tags, created, target_merge, status_label, contract_packet_sha256
| title | source_type | status | id | kind | project | work_item | inherits | refines | overrides | depends_on | contract_packet | branch | parent_branch | related_projects | tags | created | target_merge | status_label | contract_packet_sha256 | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| branch / feature-keycloak-nginx-auth-request-integration (P1A — nginx auth_request 통합) | branch-note | raw | BR-KEYCLOAK-PATTERNS-OVERVIEW-013 | project-work-item | keycloak-patterns-overview | WI-KEYCLOAK-PATTERNS-OVERVIEW-013 |
|
|
1 | feature-keycloak-nginx-auth-request-integration |
|
|
2026-05-25 | in-progress | f798f591573547bd411e1edfe92d4c5c999d10c22903ac34e81c02b0f934581f |
branch: feature-keycloak-nginx-auth-request-integration (P1A — nginx auth_request 통합)
Layer:
raw/branch-notes/— raw/project-notes/keycloak-patterns-overview Work Item의 직접 자식 branch. nginx의auth_requestdirective와 oauth2-proxy/oauth2/authendpoint contract를 subrequest 응답 단위로 분해. oauth2-proxy 자체 설정은-1-1, 네트워크 격리는-1-3에서 별도 다룬다. 본 sub-sub-branch는 문서까지만 (documented-only).status_label:in-progress
부모 (필수)
raw/project-notes/keycloak-patterns-overview
브랜치 계약 패킷
- 생성 시 프로젝트 개정:
1 - 패킷 스키마:
contract_packet: 1 - 완료 조건: nginx auth_request 통합과 4KB cookie split case가 검증된다
상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
DEC-KEYCLOAK-PATTERNS-OVERVIEW-EDGE-FORWARDAUTH-001@1 |
AP4는 oauth2-proxy ForwardAuth를 사용한다 | nginx auth_request 통합과 4KB cookie split case 검증에 적용한다 | raw/project-notes/keycloak-patterns-overview |
브랜치 지역 결정
기존 branch-local 결정은 아래
## Decision Evidence Map의 D-row가 소유하며 이 packet에서 복제하지 않는다.
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|
선언한 예외
| Override ID | Overrides | Reason | Approval | Status |
|---|
없음.
목표
P1A 패턴이 작동하는 물리적 지점은 nginx auth_request directive가 oauth2-proxy로 subrequest를 보내고, 응답 status(2xx/401)와 응답 헤더(X-Auth-Request-*)를 받아 backend로 propagate하는 그 한 줄에 모인다. 본 문서는 이 contract의 각 부품 — directive 위치, subrequest body 처리, auth_request_set 변수 추출, named location @oauth2_signin redirect, 그리고 4kb cookie/header 한도 함정 — 을 분리해서 본다.
핵심 질문:
auth_requestdirective는 어느server/locationblock에 놓아야 하는가? 모든 backendlocation에 반복해야 하는가, 아니면 상속되는가?- oauth2-proxy
/oauth2/authendpoint의 응답 contract는? 202 / 401 / 403의 의미와 nginx 측 처리. X-Auth-Request-User같은 응답 헤더를 backendproxy_pass에 어떻게 전달하는가? (auth_request_set+proxy_set_header)- access_token까지 cookie에 담으면 왜 nginx가 502를 내는가? →
proxy_buffer_size/large_client_header_buffers/ 4kb 한도와 multi-part cookie splitter.
- 이슈:
- PR:
범위
포함 범위
본 sub-sub 가 결정을 소유하는 범위. 각 항목은
Decision Evidence Map의 D-ID 로 종결.⚠️ 산출물의 지위: 본 branch 의 nginx.conf 명세는 부모 D1 이 K8s+ingress-nginx 분기를 유지하는 한 학습용 참조 구현이며 배포 대상이 아니다 (부모 D1 은 단일 VM docker-compose 를 Traefik 으로 보낸다) — §엣지·실패·의존 의 "다른 계약 의존" 첫 bullet 참조.
- nginx
auth_requestsubrequest 응답 contract (2xx allow / 401 redirect / 403 deny) 의 nginx 측 처리 — D2 - 인증 실패 응답의 route 별 분기 (browser-facing 302 vs API/machine plain 401) — D9
auth_request_set+proxy_set_header2-step 헤더 propagation 메커니즘 — D3- backend 로 전달할
X-Auth-Request-*헤더 목록 선정 — D4 - 4kb cookie/header 한도 함정 인식 + cookie split 동작 + nginx buffer 튜닝 — D5
- subrequest 의 원 요청 body 차단 (
proxy_pass_request_body off+Content-Length "") — D6 - oauth2-proxy 자체 endpoint 의 nginx 라우팅 (
location /oauth2/prefix vslocation = /oauth2/authexact 분리) — D7 - access token 을 cookie/헤더로 전달할지의 분기 (학습 단계는 양쪽 다이어그램화) — D8
제외 범위
의도적으로 제외. 각 항목은 다른 owner 가 있거나 본 branch 단계(
documented-only) 밖이다.
- oauth2-proxy 자체 구성 (
--provider,--cookie-secret,--oidc-issuer-url, OIDC code flow) → 형제 raw/branch-notes/feature-keycloak-oauth2-proxy-oidc-flow - 네트워크 격리 / header spoofing 방어 (NetworkPolicy, SG, mTLS, shared-secret 헤더) → 형제 raw/branch-notes/feature-keycloak-header-spoofing-defense. 본 branch 는 헤더를 주입 할 뿐, 그 헤더를 외부 위조로부터 지키는 것은 그쪽 결정 — D3 Open Risk 참조
- Traefik
forwardAuth비교 → 형제 raw/branch-notes/feature-keycloak-traefik-forwardauth-alternative (D1 이 본 branch 를 nginx 조합으로 한정) - K8s ingress-nginx annotation 방식 (
nginx.ingress.kubernetes.io/auth-url·auth-signin) — 2026-07-17 조사에서 공식 대안으로 확인됐으나 본 branch 는 standalone nginx config 를 다룬다. D7 의 선택 조건에 분기만 기록하고 명세는 남기지 않음 (OUT_OF_BRANCH_SCOPE) - backend RS 의 access token audience validation → raw/branch-notes/feature-keycloak-spring-rs-audience-validator (D8 Open Risk 가 이 의존을 명시)
- 실 구현 / 실측 — 본 sub-sub 는
documented-only. 모든 실측 항목은Claims To Verify로 분리되어 P3A 단계에서 수행
근거 (필수, 최소 1개+)
부모 sub-branch에서 인용한 외부 자료를 재참조 (추가 조사 없음).
- raw/official-docs/nginx-auth-request-module-official — ngx_http_auth_request_module (2xx=allow / 401|403=deny contract)
- raw/official-docs/oauth2-proxy-nginx-integration-official — oauth2-proxy 공식의 nginx 통합 가이드
- raw/official-docs/nginx-core-module-location-internal-official — D7:
internaldirective 동작(외부 요청 404) +location매칭 우선순위(exact=> prefix longest-match) 메커니즘 근거 - raw/official-docs/oauth2-proxy-endpoints-official — oauth2-proxy 자체 endpoint(
/oauth2/start·/oauth2/callback·/oauth2/sign_in·/oauth2/sign_out·/oauth2/userinfo·/oauth2/static/*·/oauth2/auth) 별 용도와 호출 주체 근거 (D7) - raw/official-docs/proxy-pass-request-body-nginx-official —
proxy_pass_request_bodydirective 의 Default(on)/Description/공식 예제 (D6 메커니즘 근거)
TODO
각 항목 옆에 증거 등급 표기.
- nginx config
location = /oauth2/authblock 작성 (internal,proxy_pass http://oauth2-proxy.upstream,proxy_pass_request_body off,proxy_set_header Content-Length "") — 등급:planned auth_request /oauth2/auth;directive를 protect할location /api/등 backend block에 추가 — 등급:planned- subrequest 응답 status별 nginx 처리: 2xx=allow / 401=
error_page 401 = @oauth2_signin/ 403=deny — 등급:planned - named location
@oauth2_signin작성:return 302 https://$host/oauth2/start?rd=$scheme://$host$request_uri;— 등급:planned - 응답 헤더 propagation:
auth_request_set $user $upstream_http_x_auth_request_user;+proxy_set_header X-User $user;— 등급:planned - 전달할 헤더 목록 정리:
X-Auth-Request-User,X-Auth-Request-Email,X-Auth-Request-Groups, (옵션)X-Auth-Request-Access-Token— 등급:planned - 4kb cookie 한도 함정: access_token cookie 포함 시 nginx 기본
proxy_buffer_size 4k/large_client_header_buffers초과 → 502/400 발생. 해결: oauth2-proxy--cookie-secret+ cookie 분할 (_oauth2_proxy_0,_1, …) 동작 이해 — 등급:planned - nginx 측 튜닝 옵션 정리:
proxy_buffer_size 16k; proxy_buffers 4 16k; large_client_header_buffers 4 16k;— 등급:planned /oauth2/callback,/oauth2/start,/oauth2/sign_out등 oauth2-proxy 자체 endpoint들이 nginx를 통과하도록location /oauth2/block 작성 — 등급:planned- subrequest의 원래 request body가 oauth2-proxy로 전달되지 않도록
proxy_pass_request_body off강제 +Content-Length빈 값 처리 — 등급:planned
진행 중 메모
작업하며 떠오른 메모.
- nginx
auth_requestcore는 별도의 authorization subrequest를 만든다. 다만 실제location = /oauth2/auth가proxy_pass로 oauth2-proxy에 전달될 때는 proxy module의 기본값이proxy_pass_request_body on이므로 원 요청 body가 upstream으로 전달될 수 있다./oauth2/auth는 헤더·쿠키만 검사하므로 실행 설정에서proxy_pass_request_body off와 빈Content-Length를 함께 명시한다. auth_request_set은 subrequest 응답 헤더에서 값을 빼와서 nginx 변수에 담는 단계. 이걸 빠뜨리면 backend는 그냥 unauthenticated 요청을 받는다.- 4kb 한도 함정은 P1A 패턴에서 가장 흔한 502 원인. 면접 질문 후보: "edge ForwardAuth 운영 중 backend가 갑자기 502 내기 시작하면 어디부터 보겠나?"
결정 사항 (decisions)
- 2026-05-25: 본 sub-sub는 nginx + oauth2-proxy 조합에 한정. Traefik의
forwardAuthmiddleware는-1-4에서 별도 비교. - 2026-05-25 (decision candidate): access_token을 cookie에 담을지(backend에서 토큰 필요) 헤더 noise로만 식별자만 넘길지는 backend 요구에 따라 갈림. 학습 단계에서는 둘 다 다이어그램화.
- 2026-07-17: 인증 실패 응답을 browser-facing route(302 redirect) 와 API/machine route(plain 401 pass-through) 로 분리 (→ D9). / 이유: 기존 D2 의 Open Risk("XHR 에 302 는 부적절")를 닫는 답을 공식 문서에서 확보. / 검토한 대안: 전 route 일괄 302(= 기존 D2 단독) — API client 가 로그인 HTML 을 받게 되어 기각. / 근거:
[[raw/official-docs/oauth2-proxy-nginx-integration-official]]#O2PN-C9(§Browser vs API Routes). - 2026-07-17: D6(subrequest body 차단)·D7(oauth2-proxy endpoint 라우팅)의
UNSUPPORTED_DECISION라벨 해소. / 근거:#O2PN-C7·#O2PN-C8(공식 nginx.conf 4-block 예제 — 기존 raw 가 산문 섹션만 인용하고 예제 블록 자체를 놓치고 있었음),#NGAR-C8(nginx.org 벤더-중립 Example Configuration — oauth2-proxy 와 독립된 2번째 공식 출처),[[raw/official-docs/proxy-pass-request-body-nginx-official]]#NGXPM-C1(defaulton시맨틱),[[raw/official-docs/oauth2-proxy-endpoints-official]]#O2EP-C1~C8(endpoint 별 용도),[[raw/official-docs/nginx-core-module-location-internal-official]]#NGCM-C1·#NGCM-C2(internal동작 + exact>prefix 매칭). / 단,/oauth2/auth에internal;을 붙이는 하드닝은 공식 예제에 없으므로UNSUPPORTED_IMPL_DECISION으로 잔존 (§구현 가이드 §1).
결정-근거 매핑
각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시. TODO 의 nginx config 패턴 (subrequest contract /
auth_request_set/ 4kb cookie 한도 / 401 redirect) 은 모두 공식 nginx 모듈 + oauth2-proxy nginx 통합 페이지에서 직접 뒷받침됨.2026-07-17 갱신: D6·D7 의
UNSUPPORTED_DECISION라벨 해소(공식 nginx.conf 예제 블록 + endpoint 목록 + nginx core module 근거 확보), D9 신설(browser vs API route 분리).선택 조건열 추가. 남은UNSUPPORTED_DECISION은 D1 하나뿐이며, 이는 학습 범위 분할이라는 조직적 결정이라 vendor doc 인용 대상이 아니다. 근거 없는 구현 detail 은 §구현 가이드의UNSUPPORTED_IMPL_DECISION4건(internal;부착 / upstream 주소 / 버퍼 수치 / browser-API 판별 기준 +@oauth2_signin목적지)으로 분리했다.
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|---|
| D1 | 본 sub-sub 는 nginx + oauth2-proxy 조합으로 한정 (Traefik forwardAuth 는 sibling sub-sub -1-4 에서 별도 비교) |
부모 D1 의 스택 선택 기준을 상속: K8s + ingress-nginx 환경 → oauth2-proxy(본 노트) / 단일 VM docker-compose → Traefik forwardAuth(sibling -1-4). 즉 본 노트의 config 는 전자 를 가정한다 — raw/branch-notes/feature-keycloak-edge-forwardauth-no-google D1 참조. ⚠️ 본 branch 가 조사한 공식 예제는 standalone nginx 기준이라 부모 D1 의 분기와 긴장 관계 — §엣지·실패·의존 참조 |
UNSUPPORTED_DECISION (학습 분할 결정 — vendor doc 인용 불요) | N/A (organizational decision) | nginx config 만 보면 Traefik 와의 비교 매트릭스 작성 시 누락된 옵션 (authResponseHeaders 등) 인식 지연 |
| D2 | /oauth2/auth subrequest 응답 contract 채택: 2xx → allow, 401 → error_page 401 = @oauth2_signin redirect, 403 → deny |
route 유형이 분기 기준: browser-facing route(사람이 브라우저로 여는 페이지) → 본 결정대로 401 을 @oauth2_signin 302 redirect 로 변환. API/machine route → 302 로 변환하지 않고 plain 401 pass-through (D9). 403 은 양쪽 공통 deny(재로그인해도 해소 안 되는 인가 실패이므로 redirect 무의미). 2xx 변종(200/202/204)은 모두 allow 로 동일 취급(NGAR-C2 does-not-prove 상 변종별 차이 없음) |
raw/official-docs/nginx-auth-request-module-official.md#NGAR-C2 (nginx 응답 코드 contract), raw/official-docs/oauth2-proxy-nginx-integration-official.md#O2PN-C2 (/oauth2/auth 202/401 spec), raw/official-docs/oauth2-proxy-nginx-integration-official.md#O2PN-C5 (401 → 302 redirect 패턴) |
official-vendor-doc (nginx F5 공식 + oauth2-proxy 공식 양측 verbatim 확인) |
XHR/API 요청에 302 redirect 반환이 적절하지 않음 (O2PN-C5 does-not-prove) — API client 별도 처리 필요 |
| D3 | 응답 헤더 propagation 메커니즘 채택: auth_request_set $user $upstream_http_x_auth_request_user + proxy_set_header X-User $user 2-step |
backend 가 사용자 신원을 필요로 하는가 가 분기 기준: 필요 → 2-step 전개(+ oauth2-proxy 를 --set-xauthrequest 로 실행해야 응답 헤더가 나옴, O2PN-C3). 불필요(단순 인증 게이팅만) → auth_request 만 두고 auth_request_set/proxy_set_header 생략 — 이 경우 backend 는 "누구인지" 모른 채 "인증됨" 만 보장받음. 대안(oauth2-proxy 를 reverse-proxy 모드로 두고 --pass-user-headers 사용, OAUTH2PROXY-C4)은 edge ForwardAuth 패턴이 아니므로 본 branch 범위 밖 |
raw/official-docs/nginx-auth-request-module-official.md#NGAR-C5 (auth_request_set + $upstream_http_* 일반 메커니즘), raw/official-docs/oauth2-proxy-nginx-integration-official.md#O2PN-C3 (X-User/X-Email 헤더 매핑) |
official-vendor-doc |
backend 가 X-User 헤더를 신뢰해도 안전한 보안 전제는 미증명 (O2PN-C3 does-not-prove) — header spoofing 방어 (sub-sub -1-3) 필요 |
| D4 | 전달 헤더 목록: X-Auth-Request-User, X-Auth-Request-Email, X-Auth-Request-Groups, (옵션) X-Auth-Request-Access-Token |
최소 전달 원칙 — backend 가 실제로 쓰는 claim 만: 식별자만 필요 → User(+Email) 만. role/group 기반 authz → Groups 추가. backend 가 토큰 자체를 필요 → Access-Token 추가(단 이는 D8 이 소유하는 분기이며 --pass-access-token 선행 필요). 헤더 이름 규약은 본 branch 가 아니라 부모 D2 가 owner (nginx 계열 X-Auth-Request-* 우선) — 부모가 규약을 바꾸면 본 행도 따라감 |
raw/official-docs/oauth2-proxy-overview-config-official.md#OAUTH2PROXY-C3 (4종 X-Auth-Request-* 응답 헤더), raw/official-docs/oauth2-proxy-overview-config-official.md#OAUTH2PROXY-C2 (--pass-access-token → X-Forwarded-Access-Token / X-Auth-Request-Access-Token), raw/official-docs/oauth2-proxy-nginx-integration-official.md#O2PN-C4 (--pass-access-token + access token forwarding 패턴) |
official-vendor-doc |
X-Auth-Request-Preferred-Username 도 spec 에 존재 (OAUTH2PROXY-C3) — 학습 시 추가 정리 필요. 또한 access token forwarding 이 RS audience validation 을 대체 안 함 (O2PN-C4 does-not-prove) |
| D5 | 4kb cookie 한도 함정 인식 + cookie split 대응 (_oauth2_proxy_0, _1, …) + nginx buffer 튜닝 (proxy_buffer_size 16k; proxy_buffers 4 16k; large_client_header_buffers 4 16k) |
세션 cookie 가 4KB 를 넘는가 가 분기 기준이며, 이는 D8 의 선택에 종속: D8-(a) access token 을 세션에 포함 → 4KB 초과 가능성 높음(특히 Keycloak 의 realm/client role claim 이 많을 때) → 버퍼 튜닝 + split cookie 대응 필수. D8-(b) 식별자만 전달 → 4KB 여유 → 기본 버퍼로 충분하나, claim 이 늘면 재검토. 한도 자체(4KB)만 공식이고 튜닝 수치(16k)는 사용자 임의 — §구현 가이드 §4 의 UNSUPPORTED_IMPL_DECISION 참조 |
raw/official-docs/oauth2-proxy-nginx-integration-official.md#O2PN-C6 (4KB 한도 + cookie split + nginx 의 first Set-Cookie 만 복사 한계) |
official-vendor-doc (oauth2-proxy 공식이 4KB 한계와 split 동작 명시) |
nginx 가 multi-part Set-Cookie 를 모두 복사하도록 하는 정확한 lua/scripting 방식은 인용 범위 밖 (O2PN-C6 does-not-prove) — 실 적용 시 lua 스크립트 작성 필요 |
| D6 | proxy_pass_request_body off + Content-Length "" 로 subrequest body 전달 차단 |
auth_request 목적지가 body 를 읽는가 가 분기 기준: /oauth2/auth 처럼 헤더/쿠키만 보고 202/401 을 내는 인증 체크 endpoint(O2PN-C2) → 본 결정대로 off. 목적지가 body 를 실제로 검사해야 하는 커스텀 인증 서비스(예: request-signing 검증)라면 → off 하면 인증이 깨지므로 default(on) 유지 — 단 그 구성은 본 branch 범위 밖(oauth2-proxy 전용 조사). 제3 옵션 proxy_request_buffering 은 전달 여부 가 아니라 버퍼링 방식 을 제어하므로 본 분기와 무관(2026-07-17 조사에서 탐색 후 기각) |
raw/official-docs/nginx-auth-request-module-official.md#NGAR-C8 (nginx 공식 "Example Configuration" 이 /auth subrequest 목적지 location 에서 이 두 directive 를 예제로 명시), raw/official-docs/proxy-pass-request-body-nginx-official.md#NGXPM-C1 (proxy_pass_request_body 의 Default 는 on — 명시적 off 없이는 원본 body 가 그대로 proxied server 로 전달된다는 directive 자체의 메커니즘), raw/official-docs/proxy-pass-request-body-nginx-official.md#NGXPM-C2 (ngx_http_proxy_module 자체 공식 문서도 off + Content-Length "" 조합을 예제로 제시 — X-Accel-Redirect 맥락이지만 NGAR-C8 과 독립된 2번째 공식 출처) |
official-vendor-doc (nginx 모듈 설계자 자신의 vendor-neutral 예제 2건 — ngx_http_auth_request_module(NGAR-C8) + ngx_http_proxy_module(NGXPM-C1/C2) 양쪽에서 독립 확인됨. directive 메커니즘(default on/off 시맨틱) 자체는 이제 2중 확인) |
NGXPM-C1/C2 는 auth_request subrequest 가 반드시 proxy_pass 기반 location 으로 라우팅된다는 것을 증명하지 않으며, 이 조합이 auth_request 서브리퀘스트에 대해 "공식적으로 필수"임을 증명하지도 않는다 (그 전용 권고는 NGAR-C8 담당, NGXPM-C2 는 X-Accel-Redirect 예제일 뿐). 설정을 없을 때 정확히 어떤 에러가 발생하는지도 여전히 증명하지 않음 (원문들은 권장/기본값 설명만 제시, 실패 모드 기술 없음) — subrequest 가 body 를 가지고 가면 POST endpoint 가 의도치 않게 트리거되거나 oauth2-proxy CPU 증가 가능하다는 추론은 여전히 needs-confirmation |
| D7 | /oauth2/callback, /oauth2/start, /oauth2/sign_out 등 oauth2-proxy 자체 endpoint 들이 nginx 를 통과하도록 location /oauth2/ block 작성 (동시에 /oauth2/auth 만 별도 location = /oauth2/auth exact block 으로 분리 가능한지는 nginx 매칭 메커니즘에 달림) |
배포 형태가 분기 기준: 단일 도메인 + standalone nginx → 본 결정(location /oauth2/ prefix + location = /oauth2/auth exact, 공식 1차 예제 O2PN-C7). K8s + ingress-nginx → nginx.conf 대신 annotation(auth-url/auth-signin) 방식 — 본 branch 범위 밖(OUT_OF_BRANCH_SCOPE, §범위 참조). 다중 앱 도메인 SSO → 도메인마다 prefix block 반복(공식 예제 주석의 X-Auth-Request-Redirect $scheme://$host$request_uri 가 이 변형을 시사). oauth2-proxy 를 별도 subdomain 에 중앙 배치하는 안은 2026-07-17 조사에서 공식 근거 부족으로 기각 (cross-domain cookie 설계가 전부 미증명 추론 영역) |
raw/official-docs/oauth2-proxy-nginx-integration-official.md#O2PN-C7 (1차 근거 — 공식 nginx.conf 예제가 location /oauth2/ prefix block 과 location = /oauth2/auth exact block 을 실제로 2-block 으로 제시), raw/official-docs/oauth2-proxy-endpoints-official.md#O2EP-C1 (/oauth2/start = OAuth cycle 시작 redirect URL), #O2EP-C2 (/oauth2/callback = IdP 가 설정하는 callback url), #O2EP-C3 (/oauth2/sign_in), #O2EP-C4 (/oauth2/sign_out), #O2EP-C5 (/oauth2/userinfo), #O2EP-C6 (/oauth2/static/*), #O2EP-C7 (/oauth2/auth 만 별도로 "for use with the Nginx auth_request directive" 라벨 — 나머지 6개 endpoint 는 이 라벨이 없음), #O2EP-C8 (전체 목록이 /oauth2 prefix 를 공유하며 --proxy-prefix 로 변경 가능), raw/official-docs/nginx-core-module-location-internal-official.md#NGCM-C1 (internal; 직접 정의 + auth_request 서브리퀘스트가 공식 "internal request" 트리거 목록에 포함됨을 확인), raw/official-docs/nginx-core-module-location-internal-official.md#NGCM-C2 (location 매칭에서 = exact match 가 발견되면 즉시 검색 종료 — prefix location /oauth2/ 과 exact location = /oauth2/auth 가 같은 /oauth2 네임스페이스 아래 충돌 없이 공존 가능한 nginx 엔진 메커니즘) |
official-vendor-doc (oauth2-proxy 공식 nginx.conf 예제 + endpoint 목록 + nginx F5 공식 core module 3중 verbatim 확인). prefix/exact 2-block 분리 자체는 O2PN-C7 공식 예제로 직접 근거 있음 — 공식이 실제로 그렇게 config 를 제시한다. 미증명인 것은 오직 /oauth2/auth 에 internal; 을 붙이는 하드닝 처방 뿐이며(공식 예제엔 internal 문자열 자체가 없음), 이는 /oauth2/auth 만 auth_request 전용 라벨이 있다는 비대칭(O2EP-C7) + exact>prefix 매칭 규칙(NGCM-C2) + auth_request 가 internal 트리거 목록에 포함(NGCM-C1)을 결합한 사용자 추론이다 |
/oauth2/auth 에 internal; 을 붙여도 안전한지는 공식 문서 미진술 (O2EP-C7 does-not-prove) — NGCM-C1+NGCM-C2 는 그런 구성이 nginx 엔진 차원에서 기술적으로 가능하다는 메커니즘만 증명하며, oauth2-proxy 공식이 그렇게 권고한다는 것은 증명하지 않는다 (oauth2-proxy 공식 nginx 통합 예제 자체는 internal; 미사용 — negative finding, NGCM-C2 Usage Boundaries 참조). nginx 측 location = /oauth2/auth { internal; ... } 격리와 location /oauth2/ { ... } 공개 block 을 분리하는 실제 구성은 여전히 사용자 추론 영역이며, oauth2-proxy-nginx-integration-official.md#O2PN-C5 (401→302 redirect) 와 결합해도 나머지 5개 endpoint(start/sign_in/userinfo/static) 의 명시적 라우팅 예제는 없음 |
| D8 | access token 을 cookie 에 담을지 헤더로만 식별자 넘길지는 backend 요구에 따라 갈림 (학습 단계는 둘 다 다이어그램화) | backend 가 토큰 자체를 필요로 하는가 가 분기 기준: (a) backend 가 RS 로서 토큰을 검증하거나 그 토큰으로 다운스트림 API 를 호출해야 함 → --pass-access-token + auth_request_set $token $upstream_http_x_auth_request_access_token. 대가: 세션이 4KB 를 넘겨 D5 의 버퍼 튜닝·split cookie 대응이 필수가 됨. (b) backend 가 "누구인지" 만 필요 → 식별자 헤더만 전달(D4), 토큰 미전달 → 세션이 작아 D5 부담 없음. 학습 단계에선 결정을 확정하지 않고 양쪽을 다이어그램화 (실 채택은 P3A) |
raw/official-docs/oauth2-proxy-overview-config-official.md#OAUTH2PROXY-C2, raw/official-docs/oauth2-proxy-nginx-integration-official.md#O2PN-C4 (둘 다 --pass-access-token 옵션의 존재만 증명) |
official-vendor-doc (옵션 존재) + 학습용 분기 결정 |
backend RS 에서 access token audience validation 을 별도 수행해야 함 (O2PN-C4 does-not-prove) — RS 측 검증 누락 시 P1A 무력화. (a) 선택 시 D5 의 4KB 함정이 가능성 이 아니라 확정 과제 로 전환됨 |
| D9 | 인증 실패 응답을 route 유형별로 분리: browser-facing route → 401 을 @oauth2_signin 302 redirect 로 변환(D2), API/machine route → error_page 401 =401 로 plain 401 을 그대로 pass-through (redirect 금지) |
요청 주체가 사람의 브라우저인가 기계인가: 사람이 브라우저로 여는 페이지 route → 302(로그인 화면으로 유도해야 UX 성립). SPA 의 XHR/fetch·CLI·서버간 호출 등 machine client route(예: location /api/) → plain 401/403(redirect 를 따라가면 로그인 HTML 을 JSON 대신 받게 되거나 CORS 로 실패). 한 서버에 두 유형이 공존하면 location 단위로 분리 — 본 결정이 D2 의 "XHR 에 302 는 부적절" Open Risk 를 닫는 답 |
raw/official-docs/oauth2-proxy-nginx-integration-official.md#O2PN-C9 (§Browser vs API Routes — "Redirecting authentication failures (302 to /oauth2/sign_in) should only be used for browser-facing routes. API or machine clients should receive a plain 401/403 response without redirect." + location /api/ { auth_request /oauth2/auth; error_page 401 =401; proxy_pass http://backend/; } 예제), raw/official-docs/oauth2-proxy-nginx-integration-official.md#O2PN-C5 (browser 측 302 패턴 — 본 결정이 분리해낸 반대편) |
official-vendor-doc (2026-07-17 조사로 확보 — 렌더링 HTML + GitHub raw markdown 2중 fetch 로 실존 확인, 이전 조사의 불일치는 재현되지 않음) |
공식은 권고(should) 일 뿐 강제 규범이 아님 (O2PN-C9 does-not-prove). 또한 브라우저 fetch 가 302 를 실제로 어떻게 follow 하는지(그리고 CORS preflight 영향)는 원문 범위 밖 — Claims To Verify 로 실측 이관. route 를 browser/API 로 어떤 기준으로 나눌지(path prefix? Accept 헤더? X-Requested-With?)는 공식 미제시 — §구현 가이드 §3 의 UNSUPPORTED_IMPL_DECISION 참조 |
구현 가이드
본 sub-sub 는
documented-only— 여기서 "구현"은 nginx.conf 를 되묻지 않고 작성할 수 있는 수준의 사전 명세를 뜻한다. 각 block 을 결정(D2~D9) + 근거 Claim ID 로 trace 하고, 공식 예제가 말하지 않는 선택은UNSUPPORTED_IMPL_DECISION+ trade-off 한 줄로 분리한다(CLAUDE.md §15.5 R2).OUT_OF_BRANCH_SCOPE 정제(R3): oauth2-proxy 자체 flag(
--set-xauthrequest·--pass-access-token·--cookie-secret)의 값과 구성 은 형제 raw/branch-notes/feature-keycloak-oauth2-proxy-oidc-flow 소유 — 본 §에는 "이 flag 가 켜져 있어야 이 nginx 설정이 성립한다"는 전제 로만 등장하고 명세는 남기지 않는다. 네트워크 격리·헤더 위조 방어는 형제 raw/branch-notes/feature-keycloak-header-spoofing-defense 소유. K8s ingress-nginx annotation 방식은 §범위 Out of scope.
0. nginx.conf 4-block 골격 (전체 구조)
Trace: 공식 1차 예제
oauth2-proxy-nginx-integration-official#O2PN-C7(prefix/exact 2-block 분리) +#O2PN-C9(API route 4번째 block) +nginx-core-module-location-internal-official#NGCM-C2(exact=가 prefix 를 이기는 매칭 규칙 — 이 골격이 성립하는 nginx 엔진 근거).
| # | block | 위상 | 소유 결정 |
|---|---|---|---|
| 1 | location /oauth2/ |
public — 브라우저가 직접 도달 (start·callback·sign_in·sign_out·userinfo·static) | D7 |
| 2 | location = /oauth2/auth |
subrequest 전용 — exact match 라 1번 prefix 에 가로채이지 않음 | D6, D7 |
| 3 | location / |
보호 대상 browser-facing route → 401 을 302 로 | D2, D3, D4 |
| 4 | location @oauth2_signin |
named location — 3번의 error_page 401 목적지 |
D2 |
| 5 | location /api/ |
보호 대상 API/machine route → 401 을 그대로 | D9 |
핵심 메커니즘: 1번과 2번은 같은 /oauth2 문자열을 공유하지만 충돌하지 않는다 — NGCM-C2 가 증명하듯 = exact match 가 발견되면 nginx 는 검색을 즉시 종료하므로 /oauth2/auth 요청은 항상 2번으로 간다. 2번 block 을 지우면 /oauth2/auth 가 1번 prefix 로 매칭되어 D6 의 body 차단이 조용히 사라진다 (§엣지 참조).
1. location = /oauth2/auth — subrequest 목적지 (D6, D7)
Trace: D6/D7 /
O2PN-C7(공식 예제의 exact block),O2PN-C8(Content-Length ""+proxy_pass_request_body off+ 인라인 주석),NGAR-C8(nginx.org 벤더-중립 Example Configuration 이 동일 패턴),NGXPM-C1(proxy_pass_request_bodydefault 는on— 명시 안 하면 body 가 전달됨),O2PN-C2(/oauth2/auth는 202/401 만 반환).
- UNSUPPORTED_IMPL_DECISION: (a)
internal;부착 여부 — 공식 예제에는internal문자열이 아예 없다(O2PN-C7negative finding).NGCM-C1이 "auth_request는 공식 internal-request 트리거 목록에 포함" 을 증명하므로 기술적으로는 안전하게 부착 가능하나, 공식이 권고한다 고 쓰면 과장이다. trade-off: 부착하면 외부 client 가/oauth2/auth를 직접 호출해 세션 유효성만 떠보는 표면이 사라지지만,/oauth2/auth를 폴링하는 모니터링이 있다면 깨진다(공식이 그런 용도를 언급하지 않아 반증·확증 모두 불가 —needs-confirmation). 학습 단계 권고: 부착하지 않고 공식 예제를 그대로 재현 → P3A 에서 하드닝 선택. (b) upstream 주소 — 공식 예제는http://127.0.0.1:4180. docker-compose 스택이면 service 명(http://oauth2-proxy:4180)이 되어야 하나 이는 배포 형태 의존이며 공식 미제시.
| directive | 값 | 근거 / 사유 |
|---|---|---|
proxy_pass |
http://127.0.0.1:4180 (공식 예제 값) |
O2PN-C7. 배포 형태에 따라 service 명으로 교체 — UNSUPPORTED_IMPL_DECISION (b) |
proxy_set_header Host |
$host |
O2PN-C7 |
proxy_set_header X-Real-IP |
$remote_addr |
O2PN-C7 |
proxy_set_header X-Forwarded-Uri |
$request_uri |
O2PN-C7 |
proxy_set_header Content-Length |
"" |
O2PN-C8, NGAR-C8 — body 차단의 짝 |
proxy_pass_request_body |
off |
O2PN-C8, NGAR-C8. 생략하면 default on(NGXPM-C1) 이라 body 가 전달됨 |
internal |
(미부착 — 학습 단계) | UNSUPPORTED_IMPL_DECISION (a) |
⚠️ 인용 경계: 공식 주석
# nginx auth_request includes headers but not body는 auth_request 의 설계 사실 을 말할 뿐, "body 를 넘기면 POST 가 오발동하거나 CPU 가 오른다"는 인과 를 말하지 않는다(O2PN-C8does-not-prove). 그 인과는 본 노트의 추론이며 §Claims To Verify 로 분리했다 — D6 의 근거로 재진술 금지.
2. location /oauth2/ — oauth2-proxy 공개 endpoint (D7)
Trace: D7 /
O2PN-C7(공식 예제의 prefix block +X-Auth-Request-Redirect헤더),oauth2-proxy-endpoints-official#O2EP-C1~C6(각 endpoint 의 용도),#O2EP-C7(/oauth2/auth만 "for use with the Nginx auth_request directive" 라벨 — 나머지 6개엔 그 제약 없음),#O2EP-C8(전체가/oauth2prefix 공유,--proxy-prefix로 변경 가능).
| endpoint | 이 block 으로 노출되는 이유 | 근거 |
|---|---|---|
/oauth2/start |
OAuth cycle 을 시작하는 redirect URL — 브라우저가 진입 | O2EP-C1 |
/oauth2/callback |
IdP 가 브라우저를 이 URL 로 되돌린다 — 외부 도달 불가면 로그인 자체가 완결 불가 | O2EP-C2 |
/oauth2/sign_in |
로그인 페이지(겸 cookie 제거) | O2EP-C3 |
/oauth2/sign_out |
세션 cookie 제거 | O2EP-C4 |
/oauth2/userinfo |
세션의 email 을 JSON 으로 반환 | O2EP-C5 |
/oauth2/static/* |
sign_in/error 페이지의 stylesheet 등 | O2EP-C6 |
proxy_set_header X-Auth-Request-Redirect $request_uri; 를 포함(O2PN-C7). 다중 도메인이면 공식 예제 주석대로 $scheme://$host$request_uri 로 확장(D7 선택 조건).
⚠️ 경계: 위 6개가 "public 이어야 한다"는 처방 은 공식 문장이 아니다. 공식은 각 endpoint 가 무엇을 하는지 만 말한다(
O2EP-C1~C6does-not-prove: 호출 주체). "그러므로 브라우저가 도달해야 한다"는 결론은/oauth2/callback의 "the oauth app will be configured with this as the callback url"(O2EP-C2) 에서만 강하게 함의되고, 나머지는 본 노트의 추론이다.
3. 보호 대상 route — browser vs API 분리 (D2, D9, D3, D4)
Trace: D2/
O2PN-C5(401 →error_page→ 302 redirect),NGAR-C2(2xx allow / 401·403 deny contract) · D9/O2PN-C9(§Browser vs API Routes +error_page 401 =401예제) · D3/NGAR-C5(auth_request_set+$upstream_http_*),O2PN-C3(X-User/X-Email매핑,--set-xauthrequest전제) · D4/OAUTH2PROXY-C3(4종X-Auth-Request-*응답 헤더).
- UNSUPPORTED_IMPL_DECISION: (a) browser/API 판별 기준 — 공식 예제는
location /api/라는 path prefix 로 나눈다(O2PN-C9). 그러나 실제 앱이 path 로 깔끔히 갈리지 않으면(같은 path 에 HTML/JSON 혼재)Accept헤더나X-Requested-With기반 분기가 필요한데 공식은 이를 제시하지 않는다. trade-off: path prefix 는 단순·명시적이나 앱 구조를 강제한다. 학습 단계는 공식대로 path prefix 채택. (b)@oauth2_signin의 목적지가/oauth2/startvs/oauth2/sign_in— 아래 별도 표 참조. (c) backend 로 넘길 헤더 이름(X-User) — 공식 예제 값이나, 본 프로젝트의 헤더 명명 규약 owner 는 부모 D2(X-Auth-Request-*우선)이므로 부모 규약과 충돌 시 부모가 이긴다.
| route 유형 | auth_request |
401 처리 | 근거 |
|---|---|---|---|
browser-facing (location /) |
auth_request /oauth2/auth; |
error_page 401 = @oauth2_signin; → 302 |
D2, O2PN-C5 |
API/machine (location /api/) |
auth_request /oauth2/auth; |
error_page 401 =401; → plain 401 pass-through |
D9, O2PN-C9 |
⚠️ 오타 아님: 두
error_page의=형태 차이(= @oauth2_signin의 space +=vs=401의 붙임)는 의도적이며 각각 공식 예제 verbatim 이다(O2PN-C5/O2PN-C9). nginxerror_page에서= @named는 named location 이 정한 코드를 따르고,=401은 응답 코드를 401 로 강제한다 — 문법이 낯설다고 임의로 통일하면 D9 가 깨진다. (error_page의=시맨틱 자체를 증명하는 claim 은 아직 raw 에 없음 — 필요 시nginx-core-module-location-internal-official에 증설)
헤더 propagation 2-step (browser route 기준, D3/D4):
| 단계 | directive | 근거 |
|---|---|---|
| 1. subrequest 응답 헤더 → nginx 변수 | auth_request_set $user $upstream_http_x_auth_request_user;auth_request_set $email $upstream_http_x_auth_request_email; |
NGAR-C5, O2PN-C3 |
| 2. 변수 → backend 요청 헤더 | proxy_set_header X-User $user;proxy_set_header X-Email $email; |
O2PN-C3 |
| (D8-a 선택 시) 토큰 | auth_request_set $token $upstream_http_x_auth_request_access_token; |
O2PN-C4 — --pass-access-token 전제 |
전제: oauth2-proxy 가 --set-xauthrequest 로 실행되지 않으면 X-Auth-Request-* 응답 헤더 자체가 나오지 않아 위 2-step 이 조용히 빈 값 이 된다(O2PN-C3). 그 flag 의 owner 는 형제 raw/branch-notes/feature-keycloak-oauth2-proxy-oidc-flow.
@oauth2_signin 목적지 — /oauth2/start vs /oauth2/sign_in (UNSUPPORTED_IMPL_DECISION (b))
본 노트의 기존 TODO 는 return 302 .../oauth2/start?rd=... 로 적혀 있으나, 공식 예제(O2PN-C5)는 /oauth2/sign_in?rd=... 를 쓴다. 둘 다 실재하는 endpoint 이며 동작이 다르다:
| 목적지 | 동작 | 언제 |
|---|---|---|
/oauth2/sign_in |
oauth2-proxy 자체 로그인 페이지 를 보여줌 (O2EP-C3) — 공식 예제 값 |
IdP 가 여럿이거나 중간 확인 화면을 원할 때 |
/oauth2/start |
OAuth cycle 을 즉시 시작 하는 redirect (O2EP-C1) — 중간 페이지 생략 |
IdP 가 Keycloak 하나뿐이라 "Sign in with…" 화면이 군더더기일 때 |
trade-off: 본 프로젝트는 IdP 가 Keycloak 단일이므로 /oauth2/start 가 클릭 1회를 줄인다. 다만 공식 예제 이탈이므로 P3A 에서 실제 UX 를 확인하고 확정할 것. 공식이 /oauth2/start 를 error_page 목적지로 권고한 문장은 없다.
4. 4kb cookie 한도 + buffer 튜닝 (D5, D8)
Trace: D5/
O2PN-C6("some provider's cookies can exceed the 4kb limit and so the OAuth2 Proxy splits these into multiple parts. Nginx normally only copies the firstSet-Cookieheader from the auth_request to the response") · D8/OAUTH2PROXY-C2,O2PN-C4(--pass-access-token존재).
- UNSUPPORTED_IMPL_DECISION: 튜닝 수치
16k는 전부 사용자 임의. 공식이 말하는 것은 4KB 한도의 존재와 cookie split 동작뿐 —proxy_buffer_size/proxy_buffers/large_client_header_buffers를 얼마로 올려야 하는지는 어떤 인용에도 없다. trade-off: 16k 는 "4KB 의 4배" 라는 경험적 여유값이며 메모리를 그만큼 더 쓴다. 실 토큰 크기를 측정해 정하는 것이 옳다(§Claims To Verify).
| 항목 | 명세 | 근거 |
|---|---|---|
| 한도 | 일부 provider 의 cookie 가 4KB 초과 → oauth2-proxy 가 _oauth2_proxy_0, _1, … 로 split |
O2PN-C6 |
| nginx 의 한계 | nginx 는 auth_request 응답에서 첫 번째 Set-Cookie 만 복사 → split 된 나머지 part 가 유실 |
O2PN-C6 |
| 버퍼 튜닝 | proxy_buffer_size 16k; proxy_buffers 4 16k; large_client_header_buffers 4 16k; |
UNSUPPORTED_IMPL_DECISION — 수치 근거 없음 |
| 적용 조건 | D8-(a) 선택 시 필수 / D8-(b) 면 여유 | D5 선택 조건 |
⚠️ 미해결: split cookie 를 nginx 가 모두 복사하게 만드는 정확한 방법(lua 등)은 인용 범위 밖(
O2PN-C6does-not-prove). 즉 D8-(a) 를 택하면 이 branch 의 명세만으로는 첫 로그인이 깨질 수 있으며, 해법은 P3A 에서 별도 조사가 필요하다 — 본 §가 닫지 못한 유일한 in-scope 구멍.
엣지·실패·의존
R4 캡처용. 정상 경로 외에 구현 중 부딪힐 실패/엣지/다른 계약 의존.
- 실패·엣지 경로:
auth_request모듈 미컴파일 (D2 전제): 모듈은 기본 빌드에 없고--with-http_auth_request_module이 필요(NGAR-C7). distribution 패키지가 항상 포함한다는 보장 없음 → 기대 동작: nginx 가auth_requestdirective 를 unknown 으로 보고 기동 실패. 착수 전nginx -V 2>&1 | grep auth_request로 확인(§Claims To Verify).location = /oauth2/auth삭제 시 조용한 회귀 (D6/D7): exact block 을 지우면/oauth2/auth가location /oauth2/prefix 로 매칭되고(NGCM-C2), 그 block 엔proxy_pass_request_body off가 없으므로 defaulton(NGXPM-C1) 으로 되돌아가 body 가 전달된다. 에러 없이 동작하므로 탐지가 어렵다.auth_request_set누락 시 조용한 인증 우회 착시 (D3): 2-step 중 1단계를 빠뜨리면 nginx 는 여전히 2xx/401 게이팅을 하지만 backend 는 빈X-User를 받는다. backend 가 헤더 유무로 신원을 판단하면 "인증됐는데 익명" 상태가 된다.--set-xauthrequest미설정도 같은 증상(O2PN-C3).- subrequest 5xx / timeout (D2):
NGAR-C3은 "Any other response code returned by the subrequest is considered an error" 만 말하고 client 가 받는 정확한 코드(500 vs 502)는 미기재. 즉 oauth2-proxy 가 죽으면 전체 요청이 fail-closed 로 차단되는데, 그 코드가 무엇인지 모른 채 알람을 설계하게 됨(§Claims To Verify). - split cookie 유실로 첫 로그인 실패 (D5/D8-a): nginx 가 첫
Set-Cookie만 복사(O2PN-C6) → 세션이 절반만 심어져 로그인 루프. 해법(lua)이 인용 범위 밖이라 본 노트만으로는 닫히지 않음. - 4KB 초과 시 502 (D5): 사용자 추론이며 미검증 —
O2PN-C6은 한도와 split 만 말한다(§Claims To Verify). - API route 에 302 를 반환 (D9): fetch/XHR 이 redirect 를 따라가 JSON 대신 로그인 HTML 을 받거나 CORS 로 실패.
O2PN-C9가 이 경로를 "should only be used for browser-facing routes" 로 명시. - 403 은 redirect 로 해소되지 않음 (D2): 401(미인증)과 달리 403(인가 실패)은 재로그인해도 그대로이므로
@oauth2_signin으로 보내면 무한 루프가 된다 → deny 유지.
- 다른 계약 의존 (대상 브랜치 + Decision ID 병기):
- 부모 raw/branch-notes/feature-keycloak-edge-forwardauth-no-google
D1(스택 선택: K8s+ingress-nginx → oauth2-proxy / 단일 VM docker-compose → Traefik) — ⚠️ 긴장 관계: 본 branch 가 채택한 공식 예제는 standalone nginx 기준인데, 부모 D1 은 단일 VM docker-compose 를 Traefik 쪽으로 보낸다. 즉 본 노트의 config 가 실제로 쓰이는 조건은 K8s + ingress-nginx 인데, 그 환경에서는 nginx.conf 대신 annotation 방식(D7 선택 조건, §범위 Out of scope)이 된다. 부모 D1 의 분기가 유지되는 한 본 §구현 가이드의 nginx.conf 는 "학습용 참조 구현"이지 배포 대상이 아니다 — 이 모순은 본 branch 가 단독으로 풀 수 없고 부모 D1 의 재검토 또는 배포 형태 확정이 선행돼야 한다. - 부모 raw/branch-notes/feature-keycloak-edge-forwardauth-no-google
D2(헤더 명명 =X-Auth-Request-*우선) — 본 branch D4 가 전달할 헤더 이름의 owner. 부모가 규약을 바꾸면 §3 의proxy_set_header이름이 따라 바뀐다. - 부모 raw/branch-notes/feature-keycloak-edge-forwardauth-no-google
D3(backend 인증 코드 제거 + ForwardAuth 위임) — 본 branch 전체의 존재 전제. 이 결정이 뒤집히면 본 노트 전부가 무효. - 형제 raw/branch-notes/feature-keycloak-oauth2-proxy-oidc-flow — oauth2-proxy flag 의 owner.
--set-xauthrequest(D3/D4 의 전제),--pass-access-token(D8-a 의 전제)이 그쪽에서 꺼지면 본 branch 의 헤더 명세가 조용히 빈다. - 형제 raw/branch-notes/feature-keycloak-oauth2-proxy-oidc-flow —
--proxy-prefix(D7 의 전제, blast radius 최대): 모든 endpoint 가/oauth2prefix 를 공유하는 것은 기본값일 뿐 변경 가능(O2EP-C8). 이 flag 가 바뀌면 헤더가 비는 정도가 아니라 §구현 가이드 §0 의 5개 block 경로 전부 +auth_request /oauth2/auth;+@oauth2_signin의/oauth2/start가 모두 조용히 404 가 된다. 형제가 이 값을 확정하기 전에 본 branch 의 경로 의존을 알려야 한다. - 형제 raw/branch-notes/feature-keycloak-header-spoofing-defense
D5— 역방향 의존: 그 branch 가 도입하려는X-Internal-Auth-Token은 본 branch 의 현재 전달 헤더 목록(D4:X-Auth-Request-*only)에 없다. 그쪽 D5 를 실 구현하려면 본 branch 의 proxy 구성이 그 헤더를 주입하도록 확장되는 것이 선행돼야 한다(신규 Decision 필요 — 현재 미존재 계약). - 형제 raw/branch-notes/feature-keycloak-header-spoofing-defense 전체 — 본 branch D3 가 주입하는
X-User를 backend 가 신뢰해도 되는 근거는 본 branch 가 제공하지 않는다(O2PN-C3does-not-prove). 네트워크 격리가 없으면 D3 는 보안적으로 무의미해진다. - raw/branch-notes/feature-keycloak-spring-rs-audience-validator — D8-(a) 채택 시 backend RS 의 audience 검증이 필수 선행(
O2PN-C4does-not-prove: token forwarding ≠ audience validation). - 범위 밖(본 branch 미소유): K8s ingress-nginx annotation 구성 / Traefik
forwardAuth매핑 / oauth2-proxy 의 provider·cookie secret 구성.
- 부모 raw/branch-notes/feature-keycloak-edge-forwardauth-no-google
검증해야 할 주장
공식 vendor docs 가 contract 의 존재를 증명해도 내 학습 시연에서 실제 nginx 빌드의 동작은 별개. 다음은 P3A 또는 실 구성 단계에서 실측해야 할 주장.
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
사용하는 nginx 패키지에 --with-http_auth_request_module 이 컴파일되어 있는지 |
NGAR-C7 은 모듈이 기본 빌드 아님 + configure 옵션 필요를 명시. distribution 별 패키지가 항상 포함한다는 보장 없음 |
nginx -V 2>&1 | grep auth_request 출력 확인 (Alpine / Debian / Amazon Linux 등 환경별 차이) |
needs-confirmation |
nginx 가 oauth2-proxy 의 multi-part Set-Cookie 헤더를 모두 응답에 복사하는지 |
O2PN-C6 은 nginx 가 기본적으로 첫 번째 Set-Cookie 만 복사한다고 명시. lua 스크립트 또는 별도 처리 필요 |
curl 로 첫 로그인 후 응답 Set-Cookie 헤더 개수 확인 + _oauth2_proxy_0, _1, ... 모두 도달했는지 검증 |
planned |
| subrequest 가 5xx 또는 timeout 시 nginx 가 정확히 어떤 응답 코드를 client 에 반환하는지 | NGAR-C3 은 "Any other response code is considered an error" 만 명시, 500 vs 502 구분 없음 |
oauth2-proxy 를 의도적으로 다운시킨 후 nginx 응답 코드 측정 | needs-confirmation |
auth_request_set 의 변수가 동일 location 내 여러 proxy_set_header 에 안정적으로 사용되는지 (변수 lifetime) |
NGAR-C5 는 변수가 authorization request 완료 후 set 됨을 명시. 그러나 location 분기 / rewrite 후의 변수 lifetime 은 인용에 없음 |
nested location + rewrite 시나리오 작성 후 backend 가 받는 X-User 헤더 값 추적 |
planned |
| 4kb cookie 한도 함정이 access token 포함 시 실제로 502 를 유발하는지 | O2PN-C6 은 4kb 한도와 cookie split 만 명시. 502 발생 메커니즘은 본 사용자 메모의 추론 |
access token 을 cookie 에 담은 상태에서 nginx 기본 buffer 로 시연 후 502 발생 여부 + 튜닝 (proxy_buffer_size 16k) 후 정상화 확인 |
needs-confirmation |
| XHR / API client 가 401 → 302 redirect 를 받았을 때의 동작 (브라우저 fetch 의 redirect follow 정책) | (2026-07-17 부분 해소) — "API client 를 별도 처리해야 한다"는 원칙 자체는 이제 O2PN-C9 로 공식 근거 확보(302 는 browser-facing route 전용, API/machine 은 plain 401/403) → D9 로 승격. 다만 브라우저 fetch 가 302 를 실제로 어떻게 follow 하는지와 CORS preflight 영향은 O2PN-C9 인용 범위 밖(does-not-prove) — 여전히 실측 필요 |
fetch / axios 로 protected endpoint 호출 후 redirect follow 동작 + CORS preflight 영향 확인. D9 적용 전(302)/후(error_page 401 =401) 응답을 비교 |
planned |
| subrequest 에 body 를 전달하면 실제로 POST endpoint 오발동 또는 oauth2-proxy CPU 증가가 발생하는지 | D6 의 Open Risk 에 있던 이 인과 서술은 2026-07-17 조사로 검증되지 않았다. O2PN-C8/NGAR-C8/NGXPM-C1 은 (1) 공식 예제가 off 를 포함한다는 사실, (2) default 가 on 이라는 시맨틱만 증명하고, body 를 넘겼을 때의 실패 모드는 어느 원문에도 없다. 순수 사용자 추론이므로 D6 의 근거 로 재진술 금지 |
proxy_pass_request_body 를 의도적으로 on 으로 둔 상태에서 대용량 body POST 를 반복 재현 → oauth2-proxy 로그(요청 body 수신 여부)와 CPU/메모리 관찰. 오발동 여부는 /oauth2/auth 가 body 를 읽는지로 판정 |
needs-confirmation |
location = /oauth2/auth 에 internal; 을 부착해도 /oauth2/start·/oauth2/callback 등 public endpoint 도달성이 깨지지 않는지 |
NGCM-C1(auth_request 가 공식 internal-request 트리거 목록에 포함) + NGCM-C2(exact match 우선)로 기술적 가능성 은 근거 확보. 그러나 "그러므로 안전하다"는 결론은 3개 사실을 결합한 추론이며 어떤 공식 문서도 직접 말하지 않는다. 공식 예제 자체는 internal; 을 쓰지 않는다(O2PN-C7 negative finding) |
internal; 부착 후 (a) 브라우저로 /oauth2/start 진입 → 로그인 완결되는지, (b) 외부에서 curl /oauth2/auth → 404 반환되는지, (c) 보호 route 의 auth_request 는 정상 동작하는지 3종 확인 |
needs-confirmation |
nginx buffer 튜닝 수치(16k)가 본 프로젝트의 실제 Keycloak 토큰 크기에 적정한지 |
공식은 4KB 한도의 존재 만 말하고(O2PN-C6) 권장 버퍼 수치를 제시하지 않는다 — 16k 는 "4KB 의 4배" 라는 사용자 임의값(§구현 가이드 §4 UNSUPPORTED_IMPL_DECISION) |
실제 Keycloak realm 의 access/refresh 토큰과 세션 cookie 크기를 측정한 뒤 필요한 버퍼를 역산. 과도한 값은 메모리 낭비이므로 실측 기반으로 확정 | planned |
@oauth2_signin 의 목적지를 /oauth2/start 로 쓰는 것(현 TODO)이 공식 예제의 /oauth2/sign_in(O2PN-C5) 대비 UX·동작상 문제가 없는지 |
두 endpoint 는 동작이 다르다 — /oauth2/start 는 OAuth cycle 즉시 시작(O2EP-C1), /oauth2/sign_in 은 로그인 페이지 표시(O2EP-C3). 공식 예제는 후자를 쓴다. 단일 IdP(Keycloak) 환경에서 전자가 낫다는 것은 사용자 판단이며 공식 권고 아님 |
두 목적지로 각각 구성해 브라우저 진입 → Keycloak 로그인 → 원 URL 복귀(rd 파라미터)까지의 클릭 수와 중간 화면 유무 비교 |
planned |
마주친 문제
- 아직 없음(문서 단계).
묶음
- raw/official-docs/nginx-auth-request-module-official
- raw/official-docs/nginx-core-module-location-internal-official
- raw/official-docs/oauth2-proxy-endpoints-official
- raw/official-docs/oauth2-proxy-nginx-integration-official
- raw/official-docs/oauth2-proxy-overview-config-official
- raw/official-docs/proxy-pass-request-body-nginx-official
- raw/official-docs/security-jwt-rfc-7519-validation
본 sub-sub-branch 는 leaf — 자식 자료 없음. Phase 3 P3A 실 구현 또는 외부 산출물 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.
오류 기록 (이 sub-sub-branch 작업 중 발생)
- (없음 — 현재 documented-only 단계)
면접 준비 (이 작업에서 나올 수 있는 면접 질문)
- (없음 — Phase 3 실 구현 단계에 누적)
관련 일일 노트
완료 후 정리
본 sub-sub-branch는 문서까지만. wiki 추출은 root branch의 비교 매트릭스 시점에 일괄 처리.
- PR 링크:
- 리뷰 메모:
- 머지 결과 / 배포 환경: 해당 없음 (문서 단계)
- wiki 추출 대상 (verified만,
wiki/projects/로만 추출):actually-implemented항목: 없음locally-verified항목: 없음prod-verified항목: 없음
- 추출하지 않을 항목 (planned / documented-only / abandoned):
- 본 sub-sub-branch 전체가
documented-only등급. P1A 부모 sub-branch와 함께 추후 비교 매트릭스 /wiki/concepts/keycloak-deployment-patterns.md에 인용되는 형태로만 활용.wiki/projects/직접 승급 없음.
- 본 sub-sub-branch 전체가