Proxy(프락시) 환경에서 client IP 를 얻기 위한 X-Forwarded-For(XFF) http header
개요
XFF 는 HTTP Header 중 하나로 HTTP Server 에 요청한 clinet 의 IP 를 식별하기 위한 사실상의 표준이다.
웹 서버나 WAS 앞에 L4 같은 Load balancers 나 Proxy server(HAProxy), caching server(Varnish), HTTP 서버용 WAS Connector(웹로직 커넥터 - mod_wl, 톰캣 커넥터 - mod_jk 등) 등이 있을 경우
이런 제품들은 웹서버/WAS 에 HTTP 나 전용 프로토콜(AJP)로 요청을 보낸후에 받은 결과를 가공하여 클라이언트에 재전송하게 된다.
이로 인해 처리한 웹 서버나 WAS에서 request.getRemoteAddr(); 등으로 클라이언트 IP를 얻을 경우 L4 나 Proxy 의 IP 를 얻게 되는데 이는 원하는 결과가 아니다.
X-Forwarded-For는 이 문제를 해결하기 위해 사용하는 http header 로 squid caching server 에서 처음 사용되었다.
다음과 같이 콤마를 구분자로 client 와 proxy IP 가 들어가게 되므로 첫번째 IP 를 가져오면 클라이언트를 식별할 수 있다.
X-Forwarded-For: client, proxy1, proxy2
WAS 에서 사용
여기까지 읽고 웹 어플리케이션을 개발할 경우 client ip 를 식별할 필요가 있다면 먼저 저 헤더가 있는지 확인한 후에 없으면 getRemoteAddr() 로 IP 를 얻으면 되겠지라고 생각할 수도 있겠지만 이게 끝은 아니다.
XFF 는 사실상의 표준이지 정식 RFC 에 포함된게 아니므로 대개는 착실하게 저 헤더를 사용하지만 엉뚱한 헤더를 사용하는 제품들이 있다.
그중에 하나인 WebLogic Connector(mod_wl) 는 저 헤더를 사용하지 않고 WL-Proxy-Client-IP 나 Proxy-Client-IP 같은 전혀 엉뚱한 헤더를 사용하므로 만약 만드는 웹 어플리케이션이
WebServer, WAS, L4, proxy 종류에 상관없이 client IP 를 잘 가져오기를 바란다면 다음과 같은 순서로 IP 를 얻어내야 한다.
String ip = request.getHeader("X-Forwarded-For"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_CLIENT_IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_X_FORWARDED_FOR"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); }
이외에도 X-Real-IP 나 X-RealIP 라는 헤더를 사용하는 제품도 있다고 한다.
Apache httpd Web Server 에서 사용
아파치 웹 서버에서는 LogFormat 지시자나 접근 권한 체크시 Remote Address 를 사용하는데 앞에 Reverse Proxy가 있다면 의도한 대로 동작하지 않으므로 XFF 헤더를 사용하도록 수정해야 한다.
로그포맷
기본 로그포맷은 %h 로 리모트 Address 를 사용하므로 다음과 같이 %{X-Forwarded-For}i 를 사용하도록 수정한다.
## 수정전 ## LogFormat "%h %l %u %t \"%r\" %>s %b" common ## 수정후 LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b" common
접근 제어(ACL)
ACL이 필요할 경우 다음과 같이 전역 변수를 설정하고 ACL 이 필요한 곳에서 이 변수를 사용하는게 편리하다.
SetEnvIf X-Forwarded-For ^192\.168\.1\. admin_1 SetEnvIf X-Forwarded-For ^192\.168\.2\. admin_2 <Files wp-config.php> order Deny,Allow deny from all </Files> ## 워드프레스 로그인은 내부 IP 에서만 허용 <Files ~ "^wp-login.php"> Order Deny,Allow Deny from all ## 내부 IP 만 관리자 로그인 허용 Allow From env=admin_1 Allow From env=admin_2 </Files>
nginx
nginx 는 --with-http_realip_module 옵션을 주고 컴파일해야 실제 ip 를 얻어올 수 있다. 배포판에 포함된 nginx 는 기본 컴파일 옵션일것이라고 생각되며 다음 명령어로 지원 여부를 확인할 수 있다.
nginx -V
nginx 가 지원한다면 다음 설정을 nginx.conf 에 넣어주면 실제 ip 가 로그 파일에 기록되며 L4나 proxy 가 여러 개일 경우 set_real_ip_from 키워드 뒤에 IP 를 개행해서 적어주면 된다.
http { set_real_ip_from 192.168.1.0/24; # proxy/L4 서버 IP set_real_ip_from 192.168.2.1; real_ip_header X-Forwarded-For; #proxy/L4 가 실제 IP 를 설정하는 HTTP Header
tomcat 의 access log
tomcat 의 access log 는 XFF 헤더를 사용하지 않으므로 아래 밸브를 추가하고 Access Log 의 패턴을 수정한다.
<Valve className="org.apache.catalina.valves.RemoteIpValve" /> <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="%{x-forwarded-for}i %l %u %t "%r" %s %b %{User-Agent}i" />
X-Real-IP 헤더를 사용하는 제품일 경우 %{x-forwarded-for}i 대신 %{x-real-ip}i 사용
%{User-Agent}i 는 로그량이 많아지므로 불필요할 경우 삭제
같이 보기
- 포워드 프록시(forward proxy) 리버스 프록시(reverse proxy) 의 차이
- 아파치 웹 서버(apache httpd) 와 톰캣 연동하기 - tomcat connector(mod_jk) , reverse proxy(mod_proxy)
- Spring 에서 client ip 가져오는 법.
Ref
- http://en.wikipedia.org/wiki/X-Forwarded-For
http://stackoverflow.com/questions/11683246/get-ip-address-of-client-in-jsp