132 lines
9.8 KiB
Markdown
132 lines
9.8 KiB
Markdown
---
|
|
id: 004dd0a2-5fb3-4f25-80c9-576f709de331
|
|
kind: REFERENCE
|
|
slug: forward-auth-identity-header-trust
|
|
title: Forward-Auth에서 Identity Header를 신뢰하기 위한 조건
|
|
topic: OAuth/OIDC 인증 경계
|
|
project: KeyCloak Patterns
|
|
status: 게시 중
|
|
version: 29
|
|
verifiedOn: 2026-08-30
|
|
studio: "https://hyeonworks.com/studio/documents/004dd0a2-5fb3-4f25-80c9-576f709de331/edit"
|
|
public: "https://hyeonworks.com/references/forward-auth-identity-header-trust"
|
|
---
|
|
|
|
# Forward-Auth에서 Identity Header를 신뢰하기 위한 조건
|
|
|
|
애플리케이션이 프록시가 전달한 사용자 정보 헤더만으로 사용자를 판단하는 구조에서는, 해당 헤더가 실제로 신뢰할 수 있는 프록시에서 전달되었다는 것을 보장해야 한다.
|
|
|
|
이를 위해 외부 사용자가 애플리케이션에 직접 접근하지 못하도록 네트워크 경로를 제한하고,
|
|
사용자가 같은 이름의 헤더를 임의로 보내더라도 프록시가 이를 제거하거나 올바른 값으로 덮어써야 한다.
|
|
또한 필요한 경우 프록시에서 전달된 요청임을 확인할 수 있는 내부용 Credential도 함께 검증한다.
|
|
|
|
세 가지는 각각 다른 구간을 막으므로 하나만 적용하지 않고 같이 구성한다.
|
|
|
|
## 관계
|
|
|
|
- **Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유**
|
|
앞에서 정리한 다섯 가지 조건이 실제 설정에 적용되어 있는지 확인한 결과.
|
|
- **Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가**
|
|
헤더를 어디까지 늘릴지가 이 기준의 미결 항목이다.
|
|
- **OAuth Token과 Application Session을 구분하는 기준**
|
|
identity 헤더를 JWT나 session과 같은 이름으로 부르지 않는다.
|
|
|
|
## 목적
|
|
|
|
외부 요청이 인증 프록시를 거쳐 애플리케이션으로 전달되는 구조에서는, 애플리케이션이 프록시가 추가한 사용자 정보 헤더를 기준으로 로그인한 사용자를 판단할 수 있다.
|
|
|
|
문제는 같은 이름의 헤더를 외부 사용자가 직접 만들어서 보낼 수도 있다는 점이다.
|
|
애플리케이션 입장에서는 전달받은 헤더만 보고 이것이 인증을 완료한 프록시가 추가한 값인지, 외부 사용자가 임의로 넣은 값인지 구분할 수 없다.
|
|
|
|
그래서 사용자 정보 헤더를 인증 근거로 사용하려면 먼저 외부 요청이 반드시 인증 프록시를 거쳐서만 애플리케이션에 도달하도록 구성해야 한다. 또한 애플리케이션이 받은 요청과 헤더가 신뢰할 수 있는 프록시를 통해 전달된 것인지 확인할 수 있는 방법도 같이 생각해야 한다.
|
|
|
|
## 규칙
|
|
|
|
### 1. 외부에서 애플리케이션과 인증 프록시에 직접 접근하지 못하게 한다
|
|
|
|
외부에서는 Edge에만 접근할 수 있도록 하고, 애플리케이션과 인증 프록시는 내부 네트워크에만 두어 Host Port로 직접 노출하지 않는다.
|
|
|
|
애플리케이션이 외부에 직접 노출되어 있으면 공격자가 Edge의 인증 과정을 거치지 않고 애플리케이션으로 요청을 보낼 수 있다.
|
|
이 경우 공격자가 사용자 정보 헤더까지 직접 만들어 보낼 수 있으므로, 애플리케이션은 해당 헤더가 인증을 거쳐 생성된 값인지 신뢰할 수 없게 된다.
|
|
그래서 사용자 정보 헤더를 인증 근거로 사용하려면 먼저 모든 외부 요청이 반드시 Edge를 거치도록 네트워크 경로부터 제한해야 한다.
|
|
|
|
### 2. client가 보낸 헤더를 항상 덮어쓴다
|
|
|
|
사용자 정보 헤더는 외부 요청에 들어 있던 값과 합치지 않고, 인증 프록시가 확인한 값으로 기존 헤더를 제거하거나 덮어쓴 뒤 애플리케이션에 전달한다.
|
|
|
|
기존 헤더와 인증 결과를 합쳐서 전달하면 공격자가 넣은 값과 프록시가 추가한 값이 하나의 헤더에 함께 포함될 수 있다.
|
|
이때 애플리케이션이 어떤 값을 사용자 정보로 사용할지는 헤더 처리 방식에 따라 달라질 수 있으므로, 인증된 값만 전달되도록 해야 한다.
|
|
|
|
또한 신뢰할 수 있는 프록시의 범위도 필요한 대상만 포함하도록 제한한다.
|
|
이 범위를 너무 넓게 설정하면 같은 내부 네트워크에 있는 다른 서비스가 신뢰받는 프록시처럼 요청을 보낼 수 있다.
|
|
특히 `Forwarded`나 `X-Forwarded-*` 헤더를 신뢰하는 구조에서는 어떤 프록시의 요청까지 신뢰할지를 먼저 좁혀 둔다.
|
|
|
|
### 3. auth endpoint는 subrequest 전용으로 둔다
|
|
|
|
이 Endpoint는 외부 사용자가 직접 호출하는 API가 아니라, 인증 과정에서 Proxy가 내부적으로 호출하기 위한 Endpoint다.
|
|
따라서 외부 요청으로는 접근할 수 없게 하고 Proxy가 생성한 내부 요청만 허용해야 한다.
|
|
|
|
Nginx에서는 해당 Location에 `internal`을 설정해 외부에서 직접 호출하는 것을 차단할 수 있다.
|
|
|
|
### 4. upstream이 헤더 존재만 보지 않는다
|
|
|
|
요청이 신뢰할 수 있는 Proxy에서 전달된 것인지 확인하기 위해, 배포할 때 설정한 내부용 Credential과 요청에 포함된 Credential을 비교한다. 이때 Credential 값의 일부가 얼마나 일치하는지에 따라 비교 시간이 크게 달라지지 않는 안전한 비교 방식을 사용한다.
|
|
|
|
이 검증을 각 Controller에서 개별적으로 처리하면 새로운 Endpoint를 추가할 때 검증 로직을 빠뜨릴 수 있기 때문에 운영 환경에서는 `Filter`, `Interceptor`, `Security Chain`과 같은 공통 처리 지점에서 모든 대상 요청에 동일한 검증이 적용되도록 구성해야 한다.
|
|
|
|
### 5. Network 격리와 헤더 검증을 모두 적용한다
|
|
|
|
네트워크 격리는 외부 사용자가 인증 경로를 우회해 애플리케이션에 직접 접근하는 것을 막는다.
|
|
헤더 검증은 내부 네트워크에서 전달된 요청이라도 사용자 정보 헤더가 신뢰할 수 있는 값인지 확인한다.
|
|
|
|
두 방식은 보호하는 구간과 대상이 다르므로 둘 다 구성한다.
|
|
|
|
### 6. 인증된 사용자 정보 헤더만 전달한다
|
|
|
|
인증 프록시가 애플리케이션으로 전달할 사용자 정보 헤더를 미리 정해 두고, 허용하지 않은 헤더는 전달하지 않는다.
|
|
새로운 헤더를 추가할 때는 해당 값이 어떤 Claim에서 만들어지는지, 여러 값이 있을 때 어떤 형식으로 전달할지, 특수 문자를 어떻게 처리할지, 허용할 최대 크기는 얼마인지, 애플리케이션에서는 그 값을 어떻게 검증하고 사용할지를 함께 정해야 한다.
|
|
|
|
현재처럼 사용자 이름과 이메일만 전달하는 구조에서는 로그인한 사용자가 누구인지는 알 수 있지만, 해당 사용자가 어떤 권한을 가지고 있는지까지 알 수는 없다.
|
|
Role을 이용해 인가까지 처리하려면 Role 정보를 어떤 방식으로 전달할지뿐만 아니라, 사용자의 Role이 변경되었을 때 기존 Proxy Session과 애플리케이션의 인가 결과에 언제 반영할지도 별도로 정해야 한다.
|
|
|
|
### 7. 요청 성공 여부가 아니라 전달된 사용자 정보를 확인한다
|
|
|
|
정상적으로 로그인된 세션에서 사용자 정보 헤더만 위조해 요청했다면, 세션 자체는 유효하므로 요청이 `200 OK`로 처리되는 것은 정상이다.
|
|
|
|
테스트에서 확인해야 하는 것은 요청의 성공이나 실패가 아니라 애플리케이션이 어떤 사용자를 인증된 사용자로 인식했는지다.
|
|
공격자가 임의로 넣은 사용자 정보가 아니라, 인증 프록시가 확인한 실제 사용자 정보가 사용되어야 한다.
|
|
응답 코드만으로는 알 수 없으므로 실제 응답에 사용된 사용자 정보까지 확인한다.
|
|
|
|
### 8. 지금 확인한 것과 운영에서 더 필요한 것을 나눠 적는다
|
|
|
|
현재 테스트 환경에서는 외부에서 애플리케이션으로 직접 접근할 수 없는지,
|
|
외부 사용자가 넣은 사용자 정보 헤더를 인증된 값으로 덮어쓰는지,
|
|
인증 Endpoint를 내부 요청으로만 호출할 수 있는지,
|
|
그리고 애플리케이션이 내부 Credential을 검증하는지까지 확인했다.
|
|
|
|
다만 실제 운영 환경에서는 추가적인 보안 구성이 필요하다.
|
|
내부 Credential과 같은 Secret은 Secret Manager 등을 통해 안전하게 주입하고 주기적으로 교체할 수 있어야 한다.
|
|
또한 Network Policy 등을 이용해 모든 요청이 정해진 인증 경로를 거치도록 제한해야 한다.
|
|
더 강한 서비스 간 인증이 필요하다면 mTLS나 Workload Identity를 적용하는 방법도 고려할 수 있다.
|
|
|
|
## 적용 조건
|
|
|
|
- upstream에 OAuth client나 JWT 검증 코드를 넣기 어려울 때
|
|
- 여러 legacy service 앞에 같은 로그인 정책을 둘 때
|
|
- edge에서 정책을 강제할 수 있을 때
|
|
- 이미 forward-auth를 쓰고 있는 구조를 점검할 때
|
|
|
|
## 예외
|
|
|
|
- backend 직접 경로나 헤더 덮어쓰기를 닫을 수 없는 환경이면 이 구조를 쓰지 않는다.
|
|
- 애플리케이션이 사용자별 API 조합과 세밀한 인가를 직접 맡아야 하면 BFF 구조를 검토한다.
|
|
- 임의 경로와 body, streaming을 그대로 넘기는 범용 reverse proxy가 필요하면 URI rewrite와 timeout, 응답 헤더 처리를 따로 설계해야 한다.
|
|
|
|
## 예시
|
|
|
|
- 외부에는 edge만 공개하고 app과 auth proxy의 port는 host에 publish하지 않는다
|
|
- 정상 session에 위조 헤더를 얹은 요청은 200을 받지만 응답의 사용자는 실제 사용자다
|
|
- 외부에서 auth endpoint를 직접 부르면 404가 된다
|
|
- upstream은 user 헤더와 internal token을 같이 확인하고 하나라도 틀리면 401을 반환한다
|
|
- 내부 검사가 특정 controller에만 있으면 새 endpoint에는 보호되지 않는다
|