Tomcat RemoteIpValve로 프록시 뒤 실제 클라이언트 IP 살리기
Nginx나 L4 뒤에 Tomcat을 두면 request.getRemoteAddr()와 액세스 로그에 프록시 IP만 찍힙니다. RemoteIpValve를 걸면 프록시가 넘긴 X-Forwarded-For를 읽어 remoteAddr을 실제 클라이언트로 바꿔 줍니다. Tomcat 11 기준이며 공식 문서상 보통 Engine 레벨(Host, Context도 가능)에 둡니다. server.xml에 이렇게 적으면 됩니다.
<Valve className="org.apache.catalina.valves.RemoteIpValve"
remoteIpHeader="x-forwarded-for"
protocolHeader="x-forwarded-proto" />동작의 핵심은 신뢰 판정입니다. 실제 접속한 홉(getRemoteAddr())이 internalProxies에 맞을 때만 헤더를 신뢰하고, X-Forwarded-For를 오른쪽부터 훑어 internalProxies에 걸리는 IP는 버린 뒤 처음 만난 외부 IP를 remoteAddr로 삼습니다. internalProxies 기본값은 사설 대역과 루프백(10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8, ::1/128 등)이라 사내망 프록시면 그대로 동작합니다. protocolHeader 기본값은 X-Forwarded-Proto라 https 여부까지 복원되어 request.isSecure()도 맞게 나옵니다. 앞단 Nginx는 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;를 세팅해야 합니다.
여기서 헷갈리기 쉬운 지점이 로그입니다. RemoteIpValve의 requestAttributesEnabled 기본값은 이미 true라 실제 IP를 request 속성으로 세팅해 둡니다. 하지만 이 속성을 읽어 액세스 로그에 반영하는 쪽은 AccessLogValve이고, 이쪽 requestAttributesEnabled 기본값은 false입니다. 그래서 로그에 실제 IP를 남기려면 RemoteIpValve가 아니라 AccessLogValve에 requestAttributesEnabled="true"를 줘야 합니다. 이 값이 false면 밸브가 세팅한 속성을 무시하고 원래 request 값(프록시 IP)을 그대로 찍습니다.
<Valve className="org.apache.catalina.valves.AccessLogValve"
requestAttributesEnabled="true"
pattern="%a %l %u %t "%r" %s %b" />주의할 점은 internalProxies에 공인 인터넷 대역을 넣지 않는 것입니다. 신뢰 범위를 넓히면 클라이언트가 위조한 X-Forwarded-For를 그대로 믿어 IP 스푸핑을 허용하게 됩니다.
🔗 https://tomcat.apache.org/tomcat-11.0-doc/config/valve.html
🔗 https://tomcat.apache.org/tomcat-11.0-doc/api/org/apache/catalina/valves/RemoteIpValve.html
로그인 후 답글을 남길 수 있습니다.