Class XForwardedRequestWrapperFactory

java.lang.Object
org.apache.wicket.protocol.http.servlet.AbstractRequestWrapperFactory
org.apache.wicket.protocol.http.servlet.XForwardedRequestWrapperFactory

Request wrapper factory to integrate "X-Forwarded-For" and "X-Forwarded-Proto" HTTP headers.

Most of the design of this Servlet Filter is a port of mod_remoteip, this servlet 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 servlet 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").

This wrapper proceeds as follows:

If the incoming request.getRemoteAddr() matches the servlet 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.

Configuration parameters:

Configuration parameters
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 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 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 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

Regular expression vs. IP address blocks: mod_remoteip allows to use address blocks (e.g. 192.168/16) to configure RemoteIPInternalProxy and RemoteIPTrustedProxy ; as the JVM doesnt have a library similar to apr_ipsubnet_test.


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:

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:

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:

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:

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.


Author:
Cyrille Le Clerc, Juergen Donnerstag