Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring server.forward-headers-strategy NATIVE vs FRAMEWORK

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:

  • https://github.com/spring-projects/spring-boot/issues/18667
  • https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.2-Release-Notes#deprecations-in-spring-boot-22
  • https://docs.spring.io/spring-framework/docs/5.1.3.RELEASE/spring-framework-reference/web.html#filters-forwarded-headers
  • https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#filters-forwarded-headers

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.

like image 314
Arun Gowda Avatar asked Jul 09 '21 14:07

Arun Gowda


1 Answers

FRAMEWORK

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

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.

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.

Embedded Tomcat auto-configured by Spring Boot

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);
    }
}

External Tomcat

// 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.

Reference

  • ServerProperties.ForwardHeadersStrategy Javadoc
  • Spring Boot Reference Documentation about forwarded headers
  • Spring HATEOAS forwarded header handling
like image 98
yejianfengblue Avatar answered Oct 21 '22 01:10

yejianfengblue