Class XForwardedRequestWrapperFactory


  • public class XForwardedRequestWrapperFactory
    extends AbstractRequestWrapperFactory
    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