어떻게 클라이언트의 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 헤더는 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에서 클라이언트의 IP 주소를 가져오는 방법은 크게 2가지가 있습니다.
request.getHeader("X-Forwarded-For")
를 통해 헤더 정보를 가져온 다음 직접 파싱하여 프록시나 로드 밸런서를 통해 기록된 클라이언트의 IP가 있는지 확인할 수 있습니다.
또는 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-strategy가 native로 설정되어 있다면 true를 반환합니다.
RemoteIpValue의 내부 코드를 살펴보면 다음과 같은 필드가 정의되어 있고
invoke()
에서 해당 헤더를 가져와 파싱하는 과정을 거치는 것을 확인할 수 있습니다.
만약 String remoteIp
가 null
이 아니라면 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: . . .
주석을 살펴보면 Forwarded
및 X-Forwarded-*
헤더에서 값을 추출하여 요청과 응답에 반영하는 동작을 수행함을 알 수 있습니다.
헤더의 범위는 기본적으로 다음과 같이 설정되어 있습니다.
Override한 getRemoteAddr()
을 살펴보면 파싱한 결과가 null
이 아닌 경우 파싱한 결과를 반환하고 null
인 경우 기존의 getRemoteAddr()
의 결과를 그대로 반환하도록 구현돼 있습니다.
이는 프록시나 로드 밸러서에 의해 IP 주소가 변경되지 않고 목적지에 도착한 경우 X-Forwarded-For
헤더에 아무것도 기록되지 않았으므로 현재 요청을 보낸 IP 주소를 그대로 반환하기 위함입니다.
만약 서버와 같은 컴퓨터에서 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 주소를 가져올 수 있게 됩니다!
HTTP 완벽 가이드
stack overflow
ServerProperties.ForwardHeadersStrategy Javadoc
Spring Boot Reference Documentation about forwarded headers
NGINX