Files
llm-wiki/raw/branch-notes/feature-keycloak-nginx-auth-request-integration.md
T

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
DEC-KEYCLOAK-PATTERNS-OVERVIEW-EDGE-FORWARDAUTH-001@1
WI-KEYCLOAK-PATTERNS-OVERVIEW-012
1 feature-keycloak-nginx-auth-request-integration
keycloak-patterns
branch
keycloak-patterns
p1a
nginx
auth-request
subrequest
cookie-limit
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_request directive와 oauth2-proxy /oauth2/auth endpoint 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 한도 함정 — 을 분리해서 본다.

핵심 질문:

  1. auth_request directive는 어느 server / location block에 놓아야 하는가? 모든 backend location에 반복해야 하는가, 아니면 상속되는가?
  2. oauth2-proxy /oauth2/auth endpoint의 응답 contract는? 202 / 401 / 403의 의미와 nginx 측 처리.
  3. X-Auth-Request-User 같은 응답 헤더를 backend proxy_pass에 어떻게 전달하는가? (auth_request_set + proxy_set_header)
  4. 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_request subrequest 응답 contract (2xx allow / 401 redirect / 403 deny) 의 nginx 측 처리 — D2
  • 인증 실패 응답의 route 별 분기 (browser-facing 302 vs API/machine plain 401) — D9
  • auth_request_set + proxy_set_header 2-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 vs location = /oauth2/auth exact 분리) — 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 validationraw/branch-notes/feature-keycloak-spring-rs-audience-validator (D8 Open Risk 가 이 의존을 명시)
  • 실 구현 / 실측 — 본 sub-sub 는 documented-only. 모든 실측 항목은 Claims To Verify 로 분리되어 P3A 단계에서 수행

근거 (필수, 최소 1개+)

부모 sub-branch에서 인용한 외부 자료를 재참조 (추가 조사 없음).

TODO

각 항목 옆에 증거 등급 표기.

  • nginx config location = /oauth2/auth block 작성 (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_request core는 별도의 authorization subrequest를 만든다. 다만 실제 location = /oauth2/authproxy_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의 forwardAuth middleware는 -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(default on 시맨틱), [[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/authinternal; 을 붙이는 하드닝은 공식 예제에 없으므로 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_DECISION 4건(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-tokenX-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/authinternal; 을 붙이는 하드닝 처방 뿐이며(공식 예제엔 internal 문자열 자체가 없음), 이는 /oauth2/auth 만 auth_request 전용 라벨이 있다는 비대칭(O2EP-C7) + exact>prefix 매칭 규칙(NGCM-C2) + auth_request 가 internal 트리거 목록에 포함(NGCM-C1)을 결합한 사용자 추론이다 /oauth2/authinternal; 을 붙여도 안전한지는 공식 문서 미진술 (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 =401plain 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_body default 는 on — 명시 안 하면 body 가 전달됨), O2PN-C2(/oauth2/auth 는 202/401 만 반환).

  • UNSUPPORTED_IMPL_DECISION: (a) internal; 부착 여부 — 공식 예제에는 internal 문자열이 아예 없다(O2PN-C7 negative 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-C8 does-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(전체가 /oauth2 prefix 공유, --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~C6 does-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/start vs /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). nginx error_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/starterror_page 목적지로 권고한 문장은 없다.

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 first Set-Cookie header 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-C6 does-not-prove). 즉 D8-(a) 를 택하면 이 branch 의 명세만으로는 첫 로그인이 깨질 수 있으며, 해법은 P3A 에서 별도 조사가 필요하다 — 본 §가 닫지 못한 유일한 in-scope 구멍.

엣지·실패·의존

R4 캡처용. 정상 경로 외에 구현 중 부딪힐 실패/엣지/다른 계약 의존.

  • 실패·엣지 경로:
    • auth_request 모듈 미컴파일 (D2 전제): 모듈은 기본 빌드에 없고 --with-http_auth_request_module 이 필요(NGAR-C7). distribution 패키지가 항상 포함한다는 보장 없음 → 기대 동작: nginx 가 auth_request directive 를 unknown 으로 보고 기동 실패. 착수 전 nginx -V 2>&1 | grep auth_request 로 확인(§Claims To Verify).
    • location = /oauth2/auth 삭제 시 조용한 회귀 (D6/D7): exact block 을 지우면 /oauth2/authlocation /oauth2/ prefix 로 매칭되고(NGCM-C2), 그 block 엔 proxy_pass_request_body off 가 없으므로 default on(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-flowoauth2-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 가 /oauth2 prefix 를 공유하는 것은 기본값일 뿐 변경 가능(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-C3 does-not-prove). 네트워크 격리가 없으면 D3 는 보안적으로 무의미해진다.
    • raw/branch-notes/feature-keycloak-spring-rs-audience-validator — D8-(a) 채택 시 backend RS 의 audience 검증이 필수 선행(O2PN-C4 does-not-prove: token forwarding ≠ audience validation).
    • 범위 밖(본 branch 미소유): K8s ingress-nginx annotation 구성 / Traefik forwardAuth 매핑 / oauth2-proxy 의 provider·cookie secret 구성.

검증해야 할 주장

공식 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/authinternal; 을 부착해도 /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

마주친 문제

  • 아직 없음(문서 단계).

묶음

본 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/ 직접 승급 없음.