The (x-)forwarded* Http-Headers family are a pseudo standard with varying and mostly lacking support in most proxies, frameworks and webservers.
This filter supports the following Http Headers:
Forwarded
as of RFC 7239- or if a
Forwarded
header is NOT found:X-Forwarded-Host
X-Forwarded-Port
X-Forwarded-Proto
- Additionally for both cases:
X-Forwarded-Prefix
is supported to adapt thegetContextPath
result.
Features:
- Supports adapting
HttpServletRequest
's scheme, host, port and prefix(contextPath)by replacing them transparently using the information from (x-)forwarded* http header(s). - Supports
HttpServletResponse.sendRedirect(location)
by rewriting it accordingly - Processing of headers and extracting parts (e.g. form
Forwarded
) is case insensitive- valid: X-Forwarded-Host, x-forwarded-host, X-forwarded-HOST,..
- Supports multiple headers of same name in request -> use Find-First-Strategy
- e.g.
X-Forwarded-Host: "hostA"
X-Forwarded-Host: "hostB" => filter will use "hostA"
- e.g.
- Supports multiple COMMA-SPACE separated values inside a headers value ->use Find-First-Strategy
- e.g. X-Forwarded-Host: "hostA, hostB" => filter will use "hostA"
- Configurable processing strategy for
X-Forwarded-Prefix
=> PREPEND or REPLACE the contextPathForwardedFilter.xForwardedPrefixStrategy=[PREPEND, REPLACE]
- Configurable header processing and removal strategy
ForwardedFilter.headerProcessingStrategy=[EVAL_AND_KEEP, EVAL_AND_REMOVE, DONT_EVAL_AND_REMOVE]
- EVAL_AND_KEEP - process headers and keep them in the list of headers for downstream processing
- EVAL_AND_REMOVE - process headers and remove them. Wont be visible any more when accessing getHeader(s)
- DONT_EVAL_AND_REMOVE - don't process the headers, just remove them.
DONT_EVAL_AND_DONT_REMOVE=> just don't activate this filter - same effect
You probably should disable all other x-forwarded processing code - like done by your underlying webserver. See: Disable other (x-)forwarded* header processing in various products
- Only slf4j, commons-lang3 and commons-collections4
- No Spring required
The JARs are available via Maven Central and JCenter.
If you are using Maven to build your project, add the following to the pom.xml
file.
<!-- https://mvnrepository.com/artifact/de.qaware.xff/x-forwarded-filter -->
<dependency>
<groupId>de.qaware.xff</groupId>
<artifactId>x-forwarded-filter</artifactId>
<version>1.0</version>
</dependency>
In case you are using Gradle to build your project, add the following to the build.gradle
file:
repositories {
jcenter()
mavenCentral()
}
dependencies {
// https://mvnrepository.com/artifact/de.qaware.xff/x-forwarded-filter
compile group: 'de.qaware.xff', name: 'x-forwarded-filter', version: '1.0'
}
Simple:
@Bean
FilterRegistrationBean forwardedHeaderFilter() {
FilterRegistrationBean frb = new FilterRegistrationBean();
frb.setFilter(new ForwardedHeaderFilter());
//must run as first filter
frb.setOrder(Ordered.HIGHEST_PRECEDENCE);
//Configuration options and their defaults
frb.addInitParameter(ForwardedHeaderFilter.ENABLE_RELATIVE_REDIRECTS_INIT_PARAM, Boolean.FALSE.toString());//false is default
frb.addInitParameter(ForwardedHeaderFilter.HEADER_PROCESSING_STRATEGY, HeaderProcessingStrategy.EVAL_AND_REMOVE.name());//EVAL_AND_REMOVE is default
frb.addInitParameter(ForwardedHeaderFilter.X_FORWARDED_PREFIX_STRATEGY, XForwardedPrefixStrategy.REPLACE.name()); //Replace is default
return frb;
}
}
Extended Configuration:
import de.qaware.xff.filter.ForwardedHeaderFilter; //warning! dont trust the autoimport as it will likley use org.springframework.web.filter.ForwardedHeaderFilter
//..
@Configuration
@ConditionalOnProperty(value = "de.qaware.xff.enabled", havingValue = "true")
public class FilterRegistrationConfiguration {
@Data
@Configuration
@ConfigurationProperties(prefix = "de.qaware.xff")
static class ForwardedHeaderFilterConfiguration{
private boolean enabled = true;
private boolean enableRelativeRedirects = false;
private HeaderProcessingStrategy headerProcessingStrategy = HeaderProcessingStrategy.EVAL_AND_KEEP;
private XForwardedPrefixStrategy xForwardedPrefixStrategy = XForwardedPrefixStrategy.PREPEND;
}
@Bean
FilterRegistrationBean forwardedHeaderFilter(ForwardedHeaderFilterConfiguration c) {
FilterRegistrationBean frb = new FilterRegistrationBean();
frb.setFilter(new ForwardedHeaderFilter());
frb.setOrder(Ordered.HIGHEST_PRECEDENCE);
frb.setEnabled(c.isEnabled());
frb.addInitParameter(ForwardedHeaderFilter.ENABLE_RELATIVE_REDIRECTS_INIT_PARAM,
Boolean.toString(c.isEnableRelativeRedirects()));
frb.addInitParameter(ForwardedHeaderFilter.HEADER_PROCESSING_STRATEGY, c.getHeaderProcessingStrategy().name());
frb.addInitParameter(ForwardedHeaderFilter.X_FORWARDED_PREFIX_STRATEGY, c.getXForwardedPrefixStrategy().name());
return frb;
}
}
And then in application.yml:
de:
qaware:
xff:
enabled: true
#enableRelativeRedirects: false
#headerProcessingStrategy: EVAL_AND_KEEP # EVAL_AND_KEEP, EVAL_AND_REMOVE, DONT_EVAL_AND_REMOVE , or disable the filter with enabled: false
#xForwardedPrefixStrategy: PREPEND # one of: PREPEND, REPLACE
<!--ForwardedHeaderFilter MUST be first filter in chain -->
<filter>
<filter-name>ForwardedHeaderFilter</filter-name>
<filter-class>de.qaware.xff.filter.ForwardedHeaderFilter</filter-class>
<init-param>
<param-name>headerProcessingStrategy</param-name>
<param-value>EVAL_AND_REMOVE</param-value>
</init-param>
<init-param>
<param-name>xForwardedPrefixStrategy</param-name>
<param-value>REPLACE</param-value>
</init-param>
<!--
<init-param>
<param-name>enableRelativeRedirects</param-name>
<param-value>false</param-value>
</init-param>
-->
</filter>
<filter-mapping>
<filter-name>ForwardedHeaderFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Disable trustedHeaderOrigin inside server.xml
<httpDispatcher trustedHeaderOrigin="none" />
- Don't register the org.springframework.web.filter.ForwardedHeaderFilter
- Set
server.use-forward-headers=false
- generically turns off the forward header handling in the underlying webserver! (e.g. tomcat's RemoteIpValve) see: "howto-use-tomcat-behind-a-proxy-server"
Don't register RemoteIpValve in TomcatEmbeddedServletContainerFactory
void configureTomcatXForwardedHandling(TomcatEmbeddedServletContainerFactory factory){
if(xForwardedHandlingByTomcat){
RemoteIpValve remoteIpValve = new RemoteIpValve();
factory.addContextValves(remoteIpValve);
}
}
- Imagine your applications sits behind a proxy or another serivce or a chain of proxies/services
- Imagine your application is reachable over different DNS names
- You never want to hardcode external URL in the backend service - ever!
Now you need, for whatever reason, the exact external URL as the client calling you sees it.
For example, you use an generic Login Proxy 'login.corp.com' in front of you business services e.g. 'biz.corp.com'. And you require 'login.corp.com' to send your user back to your service, after he's done authenticating. For this your business backend service lets call it 'biz.int.corp' (note: internal service URL), needs to tell login.corp.com the exact external URL 'biz.corp.com' your user came from. You do NOT want to hardcode the external URL in your internal service - ever! - it Kills a/b testing and you cannot expose the same service via different URLS. Its just a knightmare. But lets continue. To forward a user back to you, your backend service now needs to dyamically know the protocol, host, port and maybe the prefix form HttpServletRequest
to reconstruct the original URL - as your client connecting via the internet sees it.
Without a mechanism aware of "forwarded" headers your HttpServletRequest
does only contain the protocol, host and port of the service/proxy infront of your service - which probably is some company internal DNS address and port.
Furthermore the service/proxy in front of you may strips some prefix form your request path, for example because it aggregates & maps multiple bakcend services behind a single domain
To support this usecase of forwarding the external URL information to backend services, many proxies and webservers stated supporing various (x-)forwarded* headers. Unfortunately almost all have very bad support for these (pseudo-)standard forwarded headers, implement only parts, implement them poorly or lack the support completely. See: (x-)forwarded* support in various products
This filter will transparently take care of these concerns for you, by wrapping the HttpServletRequest
which will overwrite various methods to return the correct information.
Because most libraries and webservers have no, very bad or lacking support for these headers. The best filter i could find was the Spring-Web ForwardedHeaderFilter. This implementation is based on the filter from Spring.
Then why not use the Spring filter?
- because it requires the the full blown spring-web dependency - fine if your are already using Spring, but overkill in a small microservice just requiring this filter
- it lacks support to PREPEND the value in 'X-Forwarded-Prefix' instead of REPLACE it - is crucial for us, as we use this filter in a proxy and need to pass the values downstream
- more differences: Implementation details
- no support for "client identification" with x-forwarded-for
this filter | Spring 5.0.4 | Tomcat | IBM Websphere Liberty | Jetty | Apache mod_proxy | nginx | |
---|---|---|---|---|---|---|---|
Forwarded | YES | YES | ? | NO | YES | NO(manually with rewrite engine?) | manually with custom proxy_set_header |
X-Forwarded-Proto | YES | YES | YES | YES | YES | NO(manually with rewrite engine?) | manually with custom proxy_set_header |
X-Forwarded-Host | YES | YES | YES*1) | NO | YES | YES | manually with custom proxy_set_header |
X-Forwarded-Port | YES | YES | YES | NO | NO | NO(manually with rewrite engine?) | manually with custom proxy_set_header |
X-Forwarded-Prefix | YES | YES | NO | NO | NO | NO(manually with rewrite engine?) | manually with custom proxy_set_header |
X-Forwarded-By | NO | NO | YES | NO | NO | NO(manually with rewrite engine?) | manually with custom proxy_set_header |
X-Forwarded-For | NO | NO | YES | NO | YES | YES | YES |
X-Forwarded-Server | NO | NO | NO | NO | YES | YES | manually with custom proxy_set_header |
X-Real-IP | NO | NO | NO | NO | NO | NO(manually with rewrite engine?) | YES |
X-Proxied-Https | NO | NO | NO | NO | YES | NO(manually with rewrite engine?) | manually with custom proxy_set_header |
X-Forwarded-SSL | NO | NO | YES | NO | NO | NO? | manually with custom proxy_set_header |
Supports COMMA+SPACE separated values | YES | YES | NO | NO | YES | ? | ? |
Supports multiple Headers with same name | YES | YES | YES | NO | NO | ? | ? |
Strip forwarded header from Request |
YES(toggle) | YES(always) | ? | ? | ? | ? | ? |
Supports relative redirects in Response |
YES | YES | NO | NO | NO | ? | ? |
X-Forwarded-Prefix processing strategy |
PREPEND or REPLACE | REPLACE | ? | ? | ? | ? | ? |
*1) x-forwarded-host is supported since 9.0.23,8.5.44,7.0.97
Header | Description |
---|---|
Forwarded | same function as x-forwarded-{proto,host,port} see RFC 7239 |
X-Forwarded-Proto | The original protocol requested by the client in the Host HTTP request header |
X-Forwarded-Host | The original host requested by the client in the Host HTTP request header |
X-Forwarded-Port | The original port requested by the client in the Host HTTP request header |
X-Forwarded-Prefix | If a proxy strips a prefix from the path before forwarding it might add an X-Forwarded-Prefix header |
X-Forwarded-By | Name of the http header created by this valve to hold the list of proxies that have been processed in the incoming remoteIpHeader |
X-Forwarded-For | The IP address of the client |
X-Forwarded-Server | The hostname of the proxy server |
X-Real-IP | nginx synonym for x-forwarded-for |
X-Proxied-Https | ? |
X-Forwarded-SSL | ? |
Based on Springs ForwardedHeaderFilter but
- without Spring dependency -> easily integrable into many projects
- has toogle to NOT remove the evaluated headers from the request
- allows using this filter inside a Proxy to forward/append to these headers to downstream services (e.g. Zuul)
- Selectable processing strategy for
X-Forwarded-Prefix
PREPEND
the Context-Path of the Application with the (first) value fromX-forwarded-Prefix
- example:
REPLACE
the Context-Path of the Application with the (first) value fromX-forwarded-Prefix
- example:
Based on Gradle. First trigger of build will take longer and download the buildtool.
gradlew build
Michael Frank, michael.frank@qaware.de
Code was taken from Spring 5.0.4 Thanks to all Contributors who made that possible.
This software is provided under the Apache2.0 open source license, read the LICENSE
file for details.