My favorites | Sign in
Project Home Downloads Wiki Issues Source
Search
for
XForwardedFilter  
XForwardedFilter is a Java Servlet API port of Apache mod_remoteip to handle x-forwarded-for and x-forwarded-proto http headers
Phase-Implementation, Featured
Updated Apr 2, 2012 by clecl...@xebia.fr

XForwardedFilter is now integrated in the forthcoming Tomcat 7 as RemoteIpFilter, the XForwardedFilter provided on this project can be bundled in webapps deployed on any Java application server (IBM Websphere, JBoss, Jetty, Oracle Weblogic, Tomcat, etc)

Description

The XForwardedFilter is a Java Servlet API port of mod_remoteip, this filter replaces the apparent client remote IP address and hostname for the request with the IP address list presented by a proxy or a load balancer via a request headers (e.g. "X-Forwarded-For").

Another feature of this filter is to replace the apparent scheme (http/https) and server port with the scheme presented by a proxy or a load balancer via a request header (e.g. "X-Forwarded-Proto").

Filter Configuration

Note: In most cases, the XForwardedFilter should precede all the other filter and in particular precede logging, authentication, authorization or remote IP related filters.

XForwardedFilter property Description Equivalent mod_remoteip directive Format Default Value
remoteIPHeader Name of the Http Header read by this servlet filter that holds the list of traversed IP addresses starting from the requesting client RemoteIPHeader Compliant http header name like x-forwarded-for or X-Client-IP x-forwarded-for
allowedInternalProxies List of internal proxies ip adress. If they appear in the remoteIpHeader value, they will be trusted and will not appear in the proxiesHeader value RemoteIPInternalProxy Comma delimited list of regular expressions (in the syntax supported by the java.util.regex.Pattern library) 10\.\d{1,3}\.\d{1,3}\.\d{1,3}, 192\.168\.\d{1,3}\.\d{1,3}, 172\\.(?:1[6-9]|2\\d|3[0-1]).\\d{1,3}.\\d{1,3}, 169\.254\.\d{1,3}\.\d{1,3}, 127\.\d{1,3}\.\d{1,3}\.\d{1,3}. By default, 10/8, 192.168/16, 172.16/12, 169.254/16 and 127/8 are allowed
proxiesHeader Name of the http header created by this servlet filter to hold the list of proxies that have been processed in the incoming remoteIPHeader RemoteIPProxiesHeader Compliant http header name x-forwarded-by
trustedProxies List of trusted proxies ip adress. If they appear in the remoteIpHeader value, they will be trusted and will appear in the proxiesHeader value RemoteIPTrustedProxy Comma delimited list of regular expressions (in the syntax supported by the java.util.regex.Pattern library)
protocolHeader Name of the http header read by this servlet filter that holds the flag that this request N/A Compliant http header name like X-Forwarded-Proto, X-Forwarded-Ssl or Front-End-Https null
protocolHeaderHttpsValue Value of the protocolHeader to indicate that it is an Https request N/A String like https or ON https
httpServerPort Value returned by ServletRequest.getServerPort() when the protocolHeader indicates http protocol N/A integer 80
httpsServerPort Value returned by ServletRequest.getServerPort() when the protocolHeader indicates https protocol N/A integer 443

Basic configuration to handle x-forwarded-for

The filter will process the x-forwarded-for http header.

<web-app ...>
   ...
   <filter>
      <filter-name>XForwardedFilter</filter-name>
      <filter-class>fr.xebia.servlet.filter.XForwardedFilter</filter-class>
   </filter>
   
   <filter-mapping>
      <filter-name>XForwardedFilter</filter-name>
      <url-pattern>/*</url-pattern>
      <dispatcher>REQUEST</dispatcher>
   </filter-mapping>
   ...
</web-app>

Basic configuration to handle x-forwarded-for and x-forwarded-proto

The filter will process x-forwarded-for and x-forwarded-proto http headers. Expected value for x-forwarded-proto https connections is https (case insensitive).

<web-app ...>
   ...
   <filter>
      <filter-name>XForwardedFilter</filter-name>
      <filter-class>fr.xebia.servlet.filter.XForwardedFilter</filter-class>
      <init-param>
         <param-name>protocolHeader</param-name>
         <param-value>x-forwarded-proto</param-value>
      </init-param>
   </filter>
   
   <filter-mapping>
      <filter-name>XForwardedFilter</filter-name>
      <url-pattern>/*</url-pattern>
      <dispatcher>REQUEST</dispatcher>
   </filter-mapping>
   ...
</web-app>

Advanced samples

Sample with internal proxies

XForwardedFilter configuration:

 <filter>
    <filter-name>XForwardedFilter</filter-name>
    <filter-class>fr.xebia.servlet.filter.XForwardedFilter</filter-class>
    <init-param>
       <param-name>allowedInternalProxies</param-name><param-value>192\.168\.0\.10, 192\.168\.0\.11</param-value>
    </init-param>
    <init-param>
       <param-name>remoteIPHeader</param-name><param-value>x-forwarded-for</param-value>
    </init-param>
    <init-param>
       <param-name>remoteIPProxiesHeader</param-name><param-value>x-forwarded-by</param-value>
    </init-param>
    <init-param>
       <param-name>protocolHeader</param-name><param-value>x-forwarded-proto</param-value>
    </init-param>
 </filter>
 
 <filter-mapping>
    <filter-name>XForwardedFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
 </filter-mapping>

Request values:

property Value Before XForwardedFilter Value After XForwardedFilter
request.remoteAddr 192.168.0.10 140.211.11.130
request.header['x-forwarded-for'] 140.211.11.130, 192.168.0.10 null
request.header['x-forwarded-by'] null null
request.header['x-forwarded-proto'] https https
request.scheme http https
request.secure false true
request.serverPort 80 443

Note : x-forwarded-by header is null because only internal proxies as been traversed by the request. x-forwarded-by is null because all the proxies are trusted or internal.

Sample with trusted proxies

XForwardedFilter configuration:

 <filter>
    <filter-name>XForwardedFilter</filter-name>
    <filter-class>fr.xebia.servlet.filter.XForwardedFilter</filter-class>
    <init-param>
       <param-name>allowedInternalProxies</param-name><param-value>192\.168\.0\.10, 192\.168\.0\.11</param-value>
    </init-param>
    <init-param>
       <param-name>remoteIPHeader</param-name><param-value>x-forwarded-for</param-value>
    </init-param>
    <init-param>
       <param-name>remoteIPProxiesHeader</param-name><param-value>x-forwarded-by</param-value>
    </init-param>
    <init-param>
       <param-name>trustedProxies</param-name><param-value>proxy1, proxy2</param-value>
    </init-param>
 </filter>
 
 <filter-mapping>
    <filter-name>XForwardedFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
 </filter-mapping>

Request values:

property Value Before XForwardedFilter Value After XForwardedFilter
request.remoteAddr 192.168.0.10 140.211.11.130
request.header['x-forwarded-for'] 140.211.11.130, proxy1, proxy2 null
request.header['x-forwarded-by'] null proxy1, proxy2

Note : proxy1 and proxy2 are both trusted proxies that come in x-forwarded-for header, they both are migrated in x-forwarded-by header. x-forwarded-by is null because all the proxies are trusted or internal.

Sample with internal and trusted proxies

XForwardedFilter configuration:

 <filter>
    <filter-name>XForwardedFilter</filter-name>
    <filter-class>fr.xebia.servlet.filter.XForwardedFilter</filter-class>
    <init-param>
       <param-name>allowedInternalProxies</param-name><param-value>192\.168\.0\.10, 192\.168\.0\.11</param-value>
    </init-param>
    <init-param>
       <param-name>remoteIPHeader</param-name><param-value>x-forwarded-for</param-value>
    </init-param>
    <init-param>
       <param-name>remoteIPProxiesHeader</param-name><param-value>x-forwarded-by</param-value>
    </init-param>
    <init-param>
       <param-name>trustedProxies</param-name><param-value>proxy1, proxy2</param-value>
    </init-param>
 </filter>
 
 <filter-mapping>
    <filter-name>XForwardedFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
 </filter-mapping>

Request values:

property Value Before XForwardedFilter Value After XForwardedFilter
request.remoteAddr 192.168.0.10 140.211.11.130
request.header['x-forwarded-for'] 140.211.11.130, proxy1, proxy2, 192.168.0.10 null
request.header['x-forwarded-by'] null proxy1, proxy2

Note : proxy1 and proxy2 are both trusted proxies that come in x-forwarded-for header, they both are migrated in x-forwarded-by header. As 192.168.0.10 is an internal proxy, it does not appear in x-forwarded-by. x-forwarded-by is null because all the proxies are trusted or internal.

Sample with an untrusted proxy

XForwardedFilter configuration:

 <filter>
    <filter-name>XForwardedFilter</filter-name>
    <filter-class>fr.xebia.servlet.filter.XForwardedFilter</filter-class>
    <init-param>
       <param-name>allowedInternalProxies</param-name><param-value>192\.168\.0\.10, 192\.168\.0\.11</param-value>
    </init-param>
    <init-param>
       <param-name>remoteIPHeader</param-name><param-value>x-forwarded-for</param-value>
    </init-param>
    <init-param>
       <param-name>remoteIPProxiesHeader</param-name><param-value>x-forwarded-by</param-value>
    </init-param>
    <init-param>
       <param-name>trustedProxies</param-name><param-value>proxy1, proxy2</param-value>
    </init-param>
 </filter>
 
 <filter-mapping>
    <filter-name>XForwardedFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
 </filter-mapping>

Request values:

property Value Before XForwardedFilter Value After XForwardedFilter
request.remoteAddr 192.168.0.10 untrusted-proxy
request.header['x-forwarded-for'] 140.211.11.130, untrusted-proxy, proxy1 140.211.11.130
request.header['x-forwarded-by'] null proxy1

Note : x-forwarded-by holds the trusted proxy proxy1. x-forwarded-by holds 140.211.11.130 because untrusted-proxy is not trusted and thus, we can not trust that untrusted-proxy is the actual remote ip. request.remoteAddr is untrusted-proxy that is an IP verified by proxy1.

Install / Download

  • Maven Project, add the following to pom.xml
  • <project ...>
       ...
       <dependencies>
          ...
          <dependency>
             <groupId>fr.xebia.web</groupId>
             <artifactId>xebia-servlet-extras</artifactId>
             <version>1.0.6</version>
          </dependency>
          ...
       </dependencies>
       ...
    </project>
  • Jar : Drop the jar xebia-servlet-extras-1.0.6.jar (sources) in your web application classpath.
  • Java class : XForwardedFilter.java
  • Java project : svn checkout http://xebia-france.googlecode.com/svn/web/xebia-servlet-extras/tags/xebia-servlet-extras-1.0.6/

Implementation Details

This filter proceeds as follows:

If the incoming request.getRemoteAddr() matches the filter's list of internal proxies :

  • Loop on the comma delimited list of IPs and hostnames passed by the preceding load balancer or proxy in the given request's Http header named $remoteIPHeader (default value x-forwarded-for). Values are processed in right-to-left order.
  • For each ip/host of the list:
    • if it matches the internal proxies list, the ip/host is swallowed
    • if it matches the trusted proxies list, the ip/host is added to the created proxies header
    • otherwise, the ip/host is declared to be the remote ip and looping is stopped.
  • If the request http header named $protocolHeader (e.g. x-forwarded-for) equals to the value of protocolHeaderHttpsValue configuration parameter (default https) then request.isSecure = true, request.scheme = https and request.serverPort = 443. Note that 443 can be overwritten with the $httpsServerPort configuration parameter.

Regular expression vs. IP address blocks: mod_remoteip allows to use address blocks (e.g. 192.168/16) to configure RemoteIPInternalProxy and RemoteIPTrustedProxy ; as Tomcat doesn't have a library similar to apr_ipsubnet_test, XForwardedFilter uses regular expression to configure allowedInternalProxies and trustedProxies in the same fashion as RequestFilterFilter does.

Change Log

1.0.6 - 2012/03/30 : response.sendRedirect(location) ignores the overwritten scheme. Fix provided by Marc van Andel.

Resources

Comment by project member cyrille....@gmail.com, Jan 28, 2010

Thanks caisd. I understood your problem. I moved all the comments to the defect '(4) XForwardedFilter : request.secure and request.scheme are not forced to "false" and "http" if X-Forwarded-Proto=http'.


Sign in to add a comment
Powered by Google Project Hosting