Spring server.forward-headers-strategy NATIVE vs FRAMEWORK
Asked Answered
H

1

34

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.

Hone answered 9/7, 2021 at 14:15 Comment(0)
D
44

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

Dimaggio answered 9/7, 2021 at 15:3 Comment(4)
What exactly does it mean handling forwarded headers? How is the servlet container handling it is different from the framework handling it?Hone
@ArunGowda answer is updated with example to explain the difference.Dimaggio
Does native / framework matter in external tomcat? Because, as I mentioned in my Edit, they behave differently. In embedded tomcat, both the values work with Hateoas while in external tomcat only framework works and not native.Hone
Answering my own question above: native in external tomcat environment doesn't work. It only works with embedded tomcat. If you're using external tomcat and don't want to make any changes in external tomcat config, you have to go with frameworkHone

© 2022 - 2024 — McMap. All rights reserved.