I recently upgraded spring boot from 1.x to 2.y and was facing this issue where hateoas links were generated with http
scheme instead of https
.
Later I discovered that with spring boot 2.2+, it is mandatory to use the following property
server.forward-headers-strategy=NATIVE
which can have one of NATIVE
or FRAMEWORK
or NONE
.
NONE
property is pretty straight forward and it totally disables use of forward headers.
But there is no clear documentation on NATIVE
vs FRAMEWORK
. I've seen in many places it is mentioned that NATIVE
works the best in most cases. But there is no explanation on what exactly happens behind the scenes when we use these properties.
The documentation here doesn't give enough information for me to choose between Native/Framework. All it says is who handles the forwarded headers for corresponding values. Servlet container? or Spring framework? but it brings back to square 1. Should I let container handle it? or the framework? when Should I prefer one over the other?
I am using REST web application with external tomcat and Hateoas
for generating the links.
How do I decide whether to use NATIVE
or FRAMEWORK
property? When should one be preferred over the other and why?
My springboot version: 2.4.6
References I've already tried:
EDIT:
I tried both the solutions and framework
works for me but not native
in external tomcat environment. I created a new spring boot web application with embedded tomcat and both native
and framework
works.
FRAMEWORK
uses Spring's support for handling forwarded headers. For example, Spring Boot auto creates an ForwardedHeaderFilter
bean for Spring MVC when server.forward-headers-strategy=framework
.
@Bean
@ConditionalOnMissingFilterBean(ForwardedHeaderFilter.class)
@ConditionalOnProperty(value = "server.forward-headers-strategy", havingValue = "framework")
public FilterRegistrationBean<ForwardedHeaderFilter> forwardedHeaderFilter() {
ForwardedHeaderFilter filter = new ForwardedHeaderFilter();
FilterRegistrationBean<ForwardedHeaderFilter> registration = new FilterRegistrationBean<>(filter);
registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.ERROR);
registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registration;
}
ForwardedHeaderFilter
handles non-standard headers X-Forwarded-Host
, X-Forwarded-Port
, X-Forwarded-Proto
, X-Forwarded-Ssl
, and X-Forwarded-Prefix
.
NATIVE
uses the underlying container's native support for forwarded headers. The underlying container means tomcat, jetty, netty, etc. For example, the embedded Tomcat which is auto-configured by Spring Boot handles non-standard headers X-Forwarded-Host
, X-Forwarded-Port
, X-Forwarded-Proto
, X-Forwarded-Ssl
, but not X-Forwarded-Prefix
.
For example, API gateway runs on localhost:8080
and api service sga-booking
runs on localhost:20000
. API gateway route /sga-booking
is forwarded to api service sga-booking
.
The request to localhost:8080/sga-booking
contains headers:
forwarded = proto=http;host="localhost:8080";for="0:0:0:0:0:0:0:1%0:46706"
x-forwarded-for = 0:0:0:0:0:0:0:1%0
x-forwarded-proto = http
x-forwarded-prefix = /sga-booking
x-forwarded-port = 8080
x-forwarded-host = localhost:8080
host = 192.168.31.200:20000
When ForwardedHeaderFilter
handles forwarded headers including X-Forwarded-Prefix
, generated links starts with localhost:8080/sga-booking
. If X-Forwarded-Prefix
is not handled, generated links starts with localhost:8080
.
With property server.forward-headers-strategy=native
, method
org.springframework.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer#customizeRemoteIpValve
configures a RemoteIpValve
with properties server.tomcat.remoteip
(org.springframework.boot.autoconfigure.web.ServerProperties.Tomcat.Remoteip
) to handle forwarded headers. Note that X-Forwarded-Prefix
is not handled.
private void customizeRemoteIpValve(ConfigurableTomcatWebServerFactory factory) {
Remoteip remoteIpProperties = this.serverProperties.getTomcat().getRemoteip();
String protocolHeader = remoteIpProperties.getProtocolHeader();
String remoteIpHeader = remoteIpProperties.getRemoteIpHeader();
if (StringUtils.hasText(protocolHeader) || StringUtils.hasText(remoteIpHeader)
|| getOrDeduceUseForwardHeaders()) {
RemoteIpValve valve = new RemoteIpValve();
valve.setProtocolHeader(StringUtils.hasLength(protocolHeader) ? protocolHeader : "X-Forwarded-Proto");
if (StringUtils.hasLength(remoteIpHeader)) {
valve.setRemoteIpHeader(remoteIpHeader);
}
valve.setInternalProxies(remoteIpProperties.getInternalProxies());
try {
// X-Forwarded-Host by default
valve.setHostHeader(remoteIpProperties.getHostHeader());
}
catch (NoSuchMethodError ex) {
// Avoid failure with war deployments to Tomcat 8.5 before 8.5.44 and
// Tomcat 9 before 9.0.23
}
// X-Forwarded-Port by default
valve.setPortHeader(remoteIpProperties.getPortHeader());
valve.setProtocolHeaderHttpsValue(remoteIpProperties.getProtocolHeaderHttpsValue());
factory.addEngineValves(valve);
}
}
// Sorry I haven't play vanilla Tomcat for years after graduating from school. Below Tomcat info may be wrong.
To make external Tomcat handle forwarded headers, like what Spring Boot configures, I think A RemoteIpValve
should be configured by add
<Context>
...
<Valve className="org.apache.catalina.valves.RemoteIpValve"
hostHeader="X-Forwarded-Host"
portHeader="X-Forwarded-Port"
...
/>
...
</Context>
to Tomcat server.xml
? or context.xml
?
Find all remote ip valve attributes here. Note that no attribute is related to X-Forwarded-Prefix
.
Tomcat filter RemoteIpFilter
may have similar function. I don't know their difference.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With