- 계약 채택 — 독자 질문, 후보 29건(PROMOTE 24 · MERGE_INTO 4 · KEEP_IN_SSOT 1). 게시 중 17건은 전부 유지. 저장소 keycloak-pattern 은 패턴 넷이 브랜치로 갈라져 있어 revisions 로 tip 넷을 적었다. keycloak-session-store 는 같은 저장소 @ cdac9b8 - 게시된 기록의 redirect_uri 가 SSOT·코드와 달랐다 — OAuth2callback.html → callback.html (frontend/src/app.js 에서 확인). 계약 title 이 기록과 다른 7건도 기록 쪽으로 맞췄다 - 미작성 1건 작성 — 패턴 검증을 실제로 돌릴 때의 안전한 순서(Reference) - 리뷰 100건 반영 — 설명 뒤에 붙은 평가·차례 예고·독자 오해 가정·작성 지시를 지웠다. 삭제가 남긴 조각 4건을 고치고, 원래부터 잘려 있던 로컬 미리보기 라벨 1건도 닫았다 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
9.7 KiB
id, kind, slug, title, topic, topicName, project, status, version, verifiedOn, studio, public, sourceRevision, source
| id | kind | slug | title | topic | topicName | project | status | version | verifiedOn | studio | public | sourceRevision | source | |||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 004dd0a2-5fb3-4f25-80c9-576f709de331 | REFERENCE | forward-auth-identity-header-trust | Forward-Auth에서 Identity Header를 신뢰하기 위한 조건 | oauth-oidc-auth-boundary | OAuth/OIDC 인증 경계 | KeyCloak Patterns | 게시 중 | 29 | 2026-08-30 | https://hyeonworks.com/studio/documents/004dd0a2-5fb3-4f25-80c9-576f709de331/edit | https://hyeonworks.com/references/forward-auth-identity-header-trust | keycloak-patterns-lab@2026-08 |
|
Forward-Auth에서 Identity Header를 신뢰하기 위한 조건
애플리케이션이 프록시가 붙인 사용자 정보 헤더만 보고 로그인한 사용자를 정하는 구조라면, 그 헤더가 정말 신뢰할 수 있는 프록시에서 온 것인지를 보장해야 한다.
그러려면 외부 사용자가 애플리케이션에 직접 닿지 못하도록 네트워크 경로를 좁히고, 사용자가 같은 이름의 헤더를 실어 보내도 프록시가 그 값을 지우거나 자기가 확인한 값으로 덮어써야 한다. 프록시를 거친 요청인지 따로 확인해야 하면 내부용 자격 증명도 같이 검증한다.
관계
- Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유 앞에서 정리한 다섯 가지 조건이 실제 설정에 적용되어 있는지 확인한 결과.
- Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가 헤더를 어디까지 늘릴지가 이 기준의 미결 항목이다.
- OAuth Token과 Application Session을 구분하는 기준 사용자 정보 헤더를 JWT나 세션과 같은 이름으로 부르지 않는다.
목적
Forward-auth는 요청을 애플리케이션에 넘기기 전에 별도의 인증 엔드포인트에 이 요청을 통과시켜도 되는지 먼저 묻는 방식이다. 인증에 성공하면 프록시가 사용자 정보 헤더를 붙여 주므로, 애플리케이션은 그 헤더를 기준으로 로그인한 사용자를 판단할 수 있다.
문제는 같은 이름의 헤더를 외부 사용자가 직접 만들어 보낼 수도 있다는 것이다. 애플리케이션은 전달받은 헤더만 봐서는 인증을 마친 프록시가 붙인 값인지 외부 사용자가 임의로 넣은 값인지 구분할 수 없다.
그래서 사용자 정보 헤더를 인증 근거로 쓰려면 외부 요청이 인증 프록시를 거쳐야만 애플리케이션에 닿도록 경로부터 짜야 한다. 여기에 더해 애플리케이션이 받은 요청과 헤더가 신뢰할 수 있는 프록시를 지나온 것인지 확인할 방법도 같이 정한다.
규칙
1. 외부에서 애플리케이션과 인증 프록시에 직접 접근하지 못하게 한다
외부에 여는 것은 엣지 하나뿐이고, 애플리케이션과 인증 프록시는 내부 네트워크에만 두어 호스트 포트로 직접 노출하지 않는다.
애플리케이션이 외부에 직접 노출되어 있으면 공격자는 엣지의 인증을 거치지 않고 애플리케이션으로 요청을 보낼 수 있고, 이때 사용자 정보 헤더까지 직접 만들어 실을 수 있다. 그러면 애플리케이션은 받은 헤더가 인증을 거쳐 만들어진 값인지 신뢰할 수 없다. 사용자 정보 헤더를 인증 근거로 쓰려면 네트워크 경로부터 좁혀서 모든 외부 요청이 엣지를 지나게 해야 한다.
2. 클라이언트가 보낸 헤더를 항상 덮어쓴다
사용자 정보 헤더는 외부 요청에 실려 온 값과 합치지 않는다. 인증 프록시가 확인한 값으로 기존 헤더를 지우거나 덮어쓴 뒤에 애플리케이션으로 넘긴다.
기존 헤더와 인증 결과를 합쳐서 넘기면 공격자가 넣은 값과 프록시가 붙인 값이 한 헤더에 같이 담길 수 있다. 이때 애플리케이션이 둘 중 어느 값을 사용자 정보로 읽을지는 헤더 처리 방식에 따라 달라질 수 있으므로, 인증된 값만 남겨서 넘긴다.
신뢰할 프록시의 범위도 필요한 대상까지만 열어 둔다. 이 범위가 넓으면 같은 내부 네트워크에 있는 다른 서비스도 신뢰받는 프록시처럼 요청을 보낼 수 있다. 특히 Forwarded나 X-Forwarded-* 헤더를 믿는 구조라면 어느 프록시의 요청까지 믿을지를 먼저 좁혀 둔다.
3. 인증 엔드포인트는 하위 요청 전용으로 둔다
이 엔드포인트는 외부 사용자가 직접 부르는 API가 아니라, 인증 과정에서 프록시가 내부에서 부르려고 둔 것이다. 그래서 외부 요청으로는 닿을 수 없게 하고 프록시가 만든 내부 요청만 받는다.
Nginx에서는 해당 location에 internal을 설정해 외부에서 직접 부르는 것을 막을 수 있다.
4. 업스트림이 헤더 존재만 보지 않는다
요청이 신뢰할 수 있는 프록시에서 온 것인지 보려고, 배포할 때 설정한 내부용 자격 증명과 요청에 실려 온 값을 비교한다. 이때 값의 일부가 얼마나 일치하는지에 따라 비교에 걸리는 시간이 크게 달라지지 않는 안전한 비교 방식을 쓴다.
이 검증을 컨트롤러마다 따로 넣으면 새 엔드포인트를 추가할 때 빠뜨리기 쉽다. 운영 환경에서는 필터나 인터셉터, 시큐리티 체인처럼 대상 요청이 모두 지나는 곳 한 군데에 두어 같은 검증이 걸리게 한다.
5. 네트워크 격리와 헤더 검증을 모두 적용한다
네트워크 격리는 외부 사용자가 인증 경로를 건너뛰고 애플리케이션에 직접 접근하는 것을 막는다. 헤더 검증은 내부 네트워크에서 온 요청이라도 사용자 정보 헤더가 신뢰할 수 있는 값인지 확인한다.
6. 인증된 사용자 정보 헤더만 전달한다
인증 프록시가 애플리케이션으로 넘길 사용자 정보 헤더를 미리 정해 두고, 목록에 없는 헤더는 넘기지 않는다. 헤더를 하나 더 늘릴 때는 그 값이 어떤 클레임에서 만들어지는지, 값이 여럿일 때 어떤 형식으로 넘길지, 특수 문자를 어떻게 처리할지, 허용할 최대 크기는 얼마인지, 애플리케이션에서 그 값을 어떻게 검증하고 사용할지를 같이 정해야 한다.
사용자 이름과 이메일만 넘기는 지금 구조로는 로그인한 사용자가 누구인지까지만 알 수 있고, 그 사용자가 어떤 권한을 가졌는지는 알 수 없다. 역할(role)로 인가까지 처리하려면 역할을 어떤 방식으로 넘길지에 더해, 사용자의 역할이 바뀌었을 때 이미 열려 있는 프록시 세션과 애플리케이션의 인가 결과에 언제 반영할지도 따로 정해야 한다.
7. 요청 성공 여부가 아니라 전달된 사용자 정보를 확인한다
정상적으로 로그인된 세션에서 사용자 정보 헤더만 위조해 요청했다면 세션 자체는 유효하므로, 요청이 200 OK로 처리되는 것은 정상이다.
테스트에서 확인할 것은 요청의 성공이나 실패가 아니라 애플리케이션이 누구를 인증된 사용자로 인식했는지다. 응답 코드만으로는 알 수 없으니 응답에 실려 나온 사용자 정보까지 열어서, 공격자가 임의로 넣은 값이 아니라 인증 프록시가 확인한 실제 사용자 정보가 쓰였는지 확인한다.
8. 지금 확인한 것과 운영에서 더 필요한 것을 나눠 적는다
지금 테스트 환경에서 확인한 것은 네 가지다. 외부에서 애플리케이션으로 직접 접근할 수 없는지, 외부 사용자가 넣은 사용자 정보 헤더를 인증된 값으로 덮어쓰는지, 인증 엔드포인트를 내부 요청으로만 부를 수 있는지, 애플리케이션이 내부 자격 증명을 검증하는지까지다.
운영 환경에서는 여기에 더 얹어야 한다. 내부 자격 증명 같은 비밀값은 Secret Manager 등으로 안전하게 주입하고 주기적으로 교체할 수 있어야 하고, Network Policy 등으로 모든 요청이 정해진 인증 경로를 지나게 제한해야 한다. 서비스 사이를 더 강하게 묶어야 한다면 mTLS나 Workload Identity를 적용하는 방법도 검토할 수 있다.
적용 조건
- 업스트림에 OAuth 클라이언트나 JWT 검증 코드를 넣기 어려울 때
- 레거시 서비스 여러 개 앞에 같은 로그인 정책을 걸 때
- 엣지에서 정책을 강제할 수 있을 때
- 이미 forward-auth로 돌아가는 구조를 점검할 때
예외
- 백엔드로 바로 가는 경로나 헤더 덮어쓰기를 닫을 수 없는 환경이면 이 구조를 쓰지 않는다.
- 애플리케이션이 사용자별 API 조합과 세밀한 인가를 직접 맡아야 하면 BFF(Backend For Frontend) 구조를 검토한다.
- 임의 경로와 본문, 스트리밍을 그대로 넘기는 범용 리버스 프록시가 필요하면 URI 재작성과 타임아웃, 응답 헤더 처리를 따로 설계해야 한다.
예시
- 외부에 여는 것은 엣지뿐이고 애플리케이션과 인증 프록시의 포트는 호스트에 열지 않는다
- 정상 세션에 위조 헤더를 얹은 요청은 200을 받지만 응답에 실린 사용자는 실제 사용자다
- 외부에서 인증 엔드포인트를 직접 부르면 404가 돌아온다
- 업스트림은 사용자 헤더와 내부 토큰을 같이 확인하고 하나라도 틀리면 401을 반환한다
- 내부 검사가 특정 컨트롤러에만 있으면 새로 추가한 엔드포인트는 보호되지 않는다