1. 개요

X-Forwarded-For(XFF)는 HTTP Header 필드 중 하나이다. 목적은 Client IP 식별. Squid 캐싱 프락시 서버에서 처음 사용되었다고 합니다. 웹 서버(혹은 WAS) 앞 단에 스위치(L4), Proxy, 캐시 서버 등이 존재한다면 이들이 얻는 Client IP는 실제 얻고자 하는 Client IP가 아닐 것이다.

XFF는 이러한 문제를 해결해 줍니다. 기본적인 형태를 확인해 보겠습니다.

X-Forwarded-For: client, proxy1, proxy2

각 값들은 (콤마+공백)으로 구분됩니다. 가장 좌측이 실제 Client이며, 오른쪽으로 갈수록 추가된 프락시입니다.


2. Apache HTTP Server

아파치 웹 서버의 기본(common) 로그 포맷은 다음과 같습니다.

LogFormat "%h %l %u %t \"%r\" %>s %b" common

여기서 IP에 해당하는 항목은 %h 입니다. 이에 대한 자세한 설명을 아파치 공식 한글 문서를 통해 확인해 보겠습니다.

서버에 요청을 한 클라이언트(원격 호스트)의 IP 주소이다. HostnameLookups가 On이라면 호스트명을 찾아서 IP 주소 자리에 대신 쓴다. 그러나 이 설정은 서버를 매우 느리게 할 수 있으므로 추천하지 않는다. 호스트명을 알려면 대신 나중에 logresolve와 같은 로그를 처리하는 프로그램을 사용하는 것이 좋다. 여기에 나온 IP 주소는 사용자가 사용하는 컴퓨터 주소가 아닐 수 있다. 프록시 서버가 사용자와 서버사이에 존재한다면, 원래 컴퓨터 주소가 아니라 프록시의 주소가 기록될 것이다.

XFF를 사용하고자 한다면 로그 포맷을 다음과 같이 수정해야 합니다.

LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b" common

또는 두 필드 모두 기록할 수 있습니다.

LogFormat "%{X-Forwarded-For}i %h %l %u %t \"%r\" %>s %b" common

3. Nginx

Nginx의 경우는 컴파일 시 --with-http_realip_module 옵션을 추가해야 합니다. 그리고 nginx.conf에 다음과 같은 설정을 추가해야 합니다.

real_ip_header X-Forwarded-For;

4. Tomcat, 받는 입장에서의 처리

XFF에 Client IP를 실어보내지만 정작 request.getHeader("X-FORWARDED-FOR"); 로 꺼내지 않고 request.getRemoteAddr(); 로 꺼낸다면 결국 Client IP가 아닌 Nginx나 Apache 웹 서버의 IP를 가져오게 될 것이다.

이때는 Tomcat의 RemoteIpValve를 이용하여 Client IP 처리를 조작할 수 있다.

  • Tomcat 앞단에 위치한 웹 서버 IP가 192.168.0.10, 192.168.0.11이다.
  • Client IP는 X-Forwarded-For 헤더에 담겨있다.
 <Valve
   className="org.apache.catalina.valves.RemoteIpValve"
   internalProxies="192\.168\.0\.10|192\.168\.0\.11"
   remoteIpHeader="x-forwarded-for"
   />

위와 같이 설정하면 request.getRemoteAddr(); 을 했을 때 X-Forwarded-For 헤더에 담겨있는 IP를 확인할 수 있게 된다.


5. 표준 

그런데 XFF는 완전한 표준은 아니므로 사용에 주의해야 합니다. 즉, RFC 2616 HTTP/1.1에 정의되어 있지 않습니다.

예를 들어 보통은 Header에 XFF가 없다면 request.getRemoteAddr()을 이용하여 Client IP를 얻으려고 할 것입니다. 이런 느낌으로요.

String clientIp = req.getHeader("X-FORWARDED-FOR");
if (clientIp == null) {
  clientIp = req.getRemoteAddr();
}

만약 WebLogic을 사용하는 경우는 WebLogic Connector(wlproxy?)가 XFF를 사용하지 않으므로 다른 Header 값을 이용해야 합니다. Proxy-Client-IP 또는 WL-Proxy-Client-IP 같이 말입니다.

한편 2014년 RFC 7239를 통해 Forwarded HTTP Extension가 정의되었습니다. 관심있는 사람은 해당 RFC 문서를 확인해 보세요.


이제까지는 HTTP Header에서 XFF를 획득하는 내용에 대한 것을 언급하였는데, 사실 중요한 것은 누군가 XFF에 실어주는 것입니다. 대부분의 스위치나 프락시는 이런 기능을 지원하고 있습니다. 아마존 ELB도 그렇고, HAProxy도 마찬가지입니다.