Client IP Address와 HTTP Header

Seoyeon Kim·2024년 3월 22일
0

어떻게 클라이언트의 IP 주소를 알 수 있을까요?

우리는 request의 getRemoteAddr() 을 통해 IP 주소를 가져올 수 있습니다.

그런데 이때 가져온 주소가 정말 클라이언트의 IP 주소일까요?




사용자에 대한 정보를 얻을 수 있는 HTTP 요청 헤더 예시

헤더 이름헤더 타입설명
Form요청사용자의 이메일 주소
User-Agent요청사용자의 브라우저
Referer요청사용자가 현재 링크를 타고 온 근원 페이지
Authorization요청사용자 이름과 비밀번호
Client-ip확장(요청)클라이언트의 IP 주소
X-Forwarded-For확장(요청)클라이언트의 IP 주소
Cookie확장(요청)서버가 생성한 ID 라벨

일반적으로 클라이언트와 서버는 직접 메시지를 주고 받지 않습니다.

메시지는 복잡한 네트워크를 경유하여 목적지에 도착하게 되는데,
이 과정에서 프록시나 로드 밸런서를 거치게 되면 IP 주소가 변경될 수 있습니다.

이때 X-Forwarded-For 헤더를 통해 우리는 요청 메시지의 발자취를 확인할 수 있습니다.


X-Forwarded-For

X-Forwarded-For 헤더는 HTTP 프록시나 로드 밸런서를 통해 웹 서버에 접속하는 클라이언트의 원 IP 주소를 식별하기 위해 사용하는 헤더입니다.
클라이언트와 서버 중간에서 트래픽이 프록시나 로드 밸러서를 거치면 서버 접근 로그에는 프록시나 로드 밸러서의 IP 주소가 남게 되는데 이때 클라이언트의 IP 주소를 보기 위해 X-Forwarded-For 요청 헤더가 사용됩니다.
X-Forwarded-For 헤더는 프록시 서버를 경유할 때마다 서버의 IP 주소 정보를 쉼표로 구분해 연결해 나갑니다.

X-Forwarded-For: <client>, <proxy1>, <proxy2>

가장 오른쪽 IP 주소는 가장 마지막에 거친 프록시 IP 주소이고, 가장 왼쪽의 IP 주소는 최초 클라이언트의 IP 주소가 됩니다.
따라서 우리는 X-Forwarded-For 해더의 맨 앞 주소를 확인하면 클라이언트의 IP를 알 수 있습니다.

만약 X-Forwarded-For 헤더의 필드가 비어있다면 getRemoteAddr() 을 통해 가져온 IP 주소가 클라이언트의 IP 주소라고 판단할 수 있습니다.




Spring Boot Config

Spring Boot에서 클라이언트의 IP 주소를 가져오는 방법은 크게 2가지가 있습니다.

1. 직접 X-Forwarded-For Header 확인하기

request.getHeader("X-Forwarded-For") 를 통해 헤더 정보를 가져온 다음 직접 파싱하여 프록시나 로드 밸런서를 통해 기록된 클라이언트의 IP가 있는지 확인할 수 있습니다.


2. request.getRemoteAddr() 설정하기

또는 getRemoteAddr() 을 호출했을 때, X-Forwarded-For 헤더의 값을 전달할 수 있도록 설정할 수 있습니다.

아무것도 설정하지 않으면 기본적으로 Socket으로 연결된 IP 주소를 반환합니다.


native

컨테이너의 기본 자원을 사용하는 방식입니다.

server:
  forward-headers-strategy: native

아래의 org.springframework.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer 내부의 customizeRemoteIpValue를 보면, getOrDeduceUseForwardHeaders() 가 true일 때 새로운 RemoteIpValue 인스턴스를 생성하는 것을 확인할 수 있습니다.

getOrDeduceUseForwardHeaders()forward-headers-strategynative로 설정되어 있다면 true를 반환합니다.

RemoteIpValue의 내부 코드를 살펴보면 다음과 같은 필드가 정의되어 있고

invoke() 에서 해당 헤더를 가져와 파싱하는 과정을 거치는 것을 확인할 수 있습니다.

만약 String remoteIpnull 이 아니라면 request.setRemoteAddr() 을 통해 파싱한 결과를 설정하게 됩니다.


framework

스프링에서 제공하는 ForwardedHeaderFilter를 사용하는 방식입니다.

server:
  forward-headers-strategy: framework

Extract values from "Forwarded" and "X-Forwarded-*" headers, wrap the request and response, and make they reflect the client-originated protocol and address in the following methods: . . .

주석을 살펴보면 ForwardedX-Forwarded-* 헤더에서 값을 추출하여 요청과 응답에 반영하는 동작을 수행함을 알 수 있습니다.

헤더의 범위는 기본적으로 다음과 같이 설정되어 있습니다.

Override한 getRemoteAddr() 을 살펴보면 파싱한 결과가 null 이 아닌 경우 파싱한 결과를 반환하고 null 인 경우 기존의 getRemoteAddr() 의 결과를 그대로 반환하도록 구현돼 있습니다.

이는 프록시나 로드 밸러서에 의해 IP 주소가 변경되지 않고 목적지에 도착한 경우 X-Forwarded-For 헤더에 아무것도 기록되지 않았으므로 현재 요청을 보낸 IP 주소를 그대로 반환하기 위함입니다.




Nginx Config

만약 서버와 같은 컴퓨터에서 Nginx를 프록시로 두고 있다면 127.0.0.1 이라는 클라이언트 IP 주소를 얻게 될 수 있습니다.
이는 X-Forwarded-For 헤더를 제대로 설정해 주지 않아 발생하는 문제일 가능성이 높습니다.
이때 Nginx에 다음과 같은 설정을 추가해 줍니다.

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

$proxy_add_x_forwarded_for 는 클라이언트의 X-Forwarded-For 헤더의 필드에 변수 $remote_addr 을 쉼표로 구분하며 결합하는 동작을 수행합니다.
만약 필드가 존재하지 않는다면 변수 $remote_addr 를 새로 필드에 설정하게 됩니다.

이렇게 모든 설정을 마치면 우리는 request.getRemoteAddr() 을 통해 진짜 클라이언트의 IP 주소를 가져올 수 있게 됩니다!




Reference

HTTP 완벽 가이드
stack overflow
ServerProperties.ForwardHeadersStrategy Javadoc
Spring Boot Reference Documentation about forwarded headers
NGINX

0개의 댓글