Commit 959e1615 authored by Madhura Bhave's avatar Madhura Bhave

Provide an option to use Spring's forwarded header support

Previously, if the `server.use-forward-headers` property
was set to true, X-Forwarded-* headers support was provided
at the server level. The property has been deprecated in favor
of `server.forward-headers-strategy` which can be also be configured
to use Spring's forwarded header support apart from native server support.

Closes gh-5677
parent 33fecec4
......@@ -30,6 +30,7 @@ import java.util.Map;
import java.util.TimeZone;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.boot.convert.DurationUnit;
import org.springframework.boot.web.server.Compression;
......@@ -79,6 +80,11 @@ public class ServerProperties {
*/
private Boolean useForwardHeaders;
/**
* Strategy for handling X-Forwarded-* headers.
*/
private ForwardHeadersStrategy forwardHeadersStrategy = ForwardHeadersStrategy.NONE;
/**
* Value to use for the Server response header (if empty, no header is sent).
*/
......@@ -129,12 +135,18 @@ public class ServerProperties {
this.address = address;
}
@DeprecatedConfigurationProperty
public Boolean isUseForwardHeaders() {
return this.useForwardHeaders;
return ForwardHeadersStrategy.NATIVE.equals(this.forwardHeadersStrategy);
}
public void setUseForwardHeaders(Boolean useForwardHeaders) {
this.useForwardHeaders = useForwardHeaders;
if (useForwardHeaders != null && useForwardHeaders) {
this.forwardHeadersStrategy = ForwardHeadersStrategy.NATIVE;
}
else {
this.forwardHeadersStrategy = ForwardHeadersStrategy.NONE;
}
}
public String getServerHeader() {
......@@ -197,6 +209,14 @@ public class ServerProperties {
return this.undertow;
}
public ForwardHeadersStrategy getForwardHeadersStrategy() {
return this.forwardHeadersStrategy;
}
public void setForwardHeadersStrategy(ForwardHeadersStrategy forwardHeadersStrategy) {
this.forwardHeadersStrategy = forwardHeadersStrategy;
}
/**
* Servlet properties.
*/
......@@ -1208,4 +1228,23 @@ public class ServerProperties {
}
public enum ForwardHeadersStrategy {
/**
* Use the underlying container's native support for forwarded headers.
*/
NATIVE,
/**
* Use Spring's support for handling forwarded headers.
*/
FRAMEWORK,
/**
* Ignore X-Forwarded-* headers.
*/
NONE
}
}
......@@ -68,8 +68,7 @@ public class JettyWebServerFactoryCustomizer implements
public void customize(ConfigurableJettyWebServerFactory factory) {
ServerProperties properties = this.serverProperties;
ServerProperties.Jetty jettyProperties = properties.getJetty();
factory.setUseForwardHeaders(
getOrDeduceUseForwardHeaders(properties, this.environment));
factory.setUseForwardHeaders(getOrDeduceUseForwardHeaders());
PropertyMapper propertyMapper = PropertyMapper.get();
propertyMapper.from(jettyProperties::getAcceptors).whenNonNull()
.to(factory::setAcceptors);
......@@ -95,13 +94,14 @@ public class JettyWebServerFactoryCustomizer implements
return value > 0;
}
private boolean getOrDeduceUseForwardHeaders(ServerProperties serverProperties,
Environment environment) {
if (serverProperties.isUseForwardHeaders() != null) {
return serverProperties.isUseForwardHeaders();
private boolean getOrDeduceUseForwardHeaders() {
if (this.serverProperties.getForwardHeadersStrategy()
.equals(ServerProperties.ForwardHeadersStrategy.NONE)) {
CloudPlatform platform = CloudPlatform.getActive(this.environment);
return platform != null && platform.isUsingForwardHeaders();
}
CloudPlatform platform = CloudPlatform.getActive(environment);
return platform != null && platform.isUsingForwardHeaders();
return this.serverProperties.getForwardHeadersStrategy()
.equals(ServerProperties.ForwardHeadersStrategy.NATIVE);
}
private void customizeConnectionTimeout(ConfigurableJettyWebServerFactory factory,
......
......@@ -58,8 +58,7 @@ public class NettyWebServerFactoryCustomizer
@Override
public void customize(NettyReactiveWebServerFactory factory) {
factory.setUseForwardHeaders(
getOrDeduceUseForwardHeaders(this.serverProperties, this.environment));
factory.setUseForwardHeaders(getOrDeduceUseForwardHeaders());
PropertyMapper propertyMapper = PropertyMapper.get();
propertyMapper.from(this.serverProperties::getMaxHttpHeaderSize).whenNonNull()
.asInt(DataSize::toBytes)
......@@ -70,13 +69,14 @@ public class NettyWebServerFactoryCustomizer
.addServerCustomizers(getConnectionTimeOutCustomizer(duration)));
}
private boolean getOrDeduceUseForwardHeaders(ServerProperties serverProperties,
Environment environment) {
if (serverProperties.isUseForwardHeaders() != null) {
return serverProperties.isUseForwardHeaders();
private boolean getOrDeduceUseForwardHeaders() {
if (this.serverProperties.getForwardHeadersStrategy()
.equals(ServerProperties.ForwardHeadersStrategy.NONE)) {
CloudPlatform platform = CloudPlatform.getActive(this.environment);
return platform != null && platform.isUsingForwardHeaders();
}
CloudPlatform platform = CloudPlatform.getActive(environment);
return platform != null && platform.isUsingForwardHeaders();
return this.serverProperties.getForwardHeadersStrategy()
.equals(ServerProperties.ForwardHeadersStrategy.NATIVE);
}
private void customizeMaxHttpHeaderSize(NettyReactiveWebServerFactory factory,
......
......@@ -188,11 +188,13 @@ public class TomcatWebServerFactoryCustomizer implements
}
private boolean getOrDeduceUseForwardHeaders() {
if (this.serverProperties.isUseForwardHeaders() != null) {
return this.serverProperties.isUseForwardHeaders();
if (this.serverProperties.getForwardHeadersStrategy()
.equals(ServerProperties.ForwardHeadersStrategy.NONE)) {
CloudPlatform platform = CloudPlatform.getActive(this.environment);
return platform != null && platform.isUsingForwardHeaders();
}
CloudPlatform platform = CloudPlatform.getActive(this.environment);
return platform != null && platform.isUsingForwardHeaders();
return this.serverProperties.getForwardHeadersStrategy()
.equals(ServerProperties.ForwardHeadersStrategy.NATIVE);
}
@SuppressWarnings("rawtypes")
......
......@@ -124,11 +124,13 @@ public class UndertowWebServerFactoryCustomizer implements
}
private boolean getOrDeduceUseForwardHeaders() {
if (this.serverProperties.isUseForwardHeaders() != null) {
return this.serverProperties.isUseForwardHeaders();
if (this.serverProperties.getForwardHeadersStrategy()
.equals(ServerProperties.ForwardHeadersStrategy.NONE)) {
CloudPlatform platform = CloudPlatform.getActive(this.environment);
return platform != null && platform.isUsingForwardHeaders();
}
CloudPlatform platform = CloudPlatform.getActive(this.environment);
return platform != null && platform.isUsingForwardHeaders();
return this.serverProperties.getForwardHeadersStrategy()
.equals(ServerProperties.ForwardHeadersStrategy.NATIVE);
}
}
......@@ -25,6 +25,7 @@ import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
......@@ -37,6 +38,7 @@ import org.springframework.core.Ordered;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.http.ReactiveHttpInputMessage;
import org.springframework.util.ObjectUtils;
import org.springframework.web.server.adapter.ForwardedHeaderTransformer;
/**
* {@link EnableAutoConfiguration Auto-configuration} for a reactive web server.
......@@ -62,6 +64,13 @@ public class ReactiveWebServerFactoryAutoConfiguration {
return new ReactiveWebServerFactoryCustomizer(serverProperties);
}
@Bean
@ConditionalOnProperty(value = "server.forward-headers-strategy",
havingValue = "framework")
public ForwardedHeaderTransformer forwardedHeaderTransformer() {
return new ForwardedHeaderTransformer();
}
/**
* Registers a {@link WebServerFactoryCustomizerBeanPostProcessor}. Registered via
* {@link ImportBeanDefinitionRegistrar} for early registration.
......
......@@ -16,6 +16,7 @@
package org.springframework.boot.autoconfigure.web.servlet;
import javax.servlet.DispatcherType;
import javax.servlet.ServletRequest;
import org.springframework.beans.BeansException;
......@@ -27,12 +28,14 @@ import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.server.ErrorPageRegistrarBeanPostProcessor;
import org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
......@@ -40,6 +43,7 @@ import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.Ordered;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ObjectUtils;
import org.springframework.web.filter.ForwardedHeaderFilter;
/**
* {@link EnableAutoConfiguration Auto-configuration} for servlet web servers.
......@@ -74,6 +78,19 @@ public class ServletWebServerFactoryAutoConfiguration {
return new TomcatServletWebServerFactoryCustomizer(serverProperties);
}
@Bean
@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;
}
/**
* Registers a {@link WebServerFactoryCustomizerBeanPostProcessor}. Registered via
* {@link ImportBeanDefinitionRegistrar} for early registration.
......
......@@ -34,6 +34,7 @@ import org.springframework.context.ApplicationContextException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.web.server.adapter.ForwardedHeaderTransformer;
import static org.assertj.core.api.Assertions.assertThat;
......@@ -42,6 +43,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Brian Clozel
* @author Raheela Aslam
* @author Madhura Bhave
*/
public class ReactiveWebServerFactoryAutoConfigurationTests {
......@@ -147,6 +149,22 @@ public class ReactiveWebServerFactoryAutoConfigurationTests {
});
}
@Test
public void forwardedHeaderTransformerShouldBeConfigured() {
this.contextRunner.withUserConfiguration(HttpHandlerConfiguration.class)
.withPropertyValues("server.forward-headers-strategy=framework")
.run((context) -> assertThat(context)
.hasSingleBean(ForwardedHeaderTransformer.class));
}
@Test
public void forwardedHeaderTransformerWhenStrategyNotFilterShouldNotBeConfigured() {
this.contextRunner.withUserConfiguration(HttpHandlerConfiguration.class)
.withPropertyValues("server.forward-headers-strategy=native")
.run((context) -> assertThat(context)
.doesNotHaveBean(ForwardedHeaderTransformer.class));
}
@Configuration(proxyBeanMethods = false)
protected static class HttpHandlerConfiguration {
......
......@@ -16,6 +16,7 @@
package org.springframework.boot.autoconfigure.web.servlet;
import javax.servlet.Filter;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
......@@ -35,6 +36,7 @@ import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
......@@ -43,6 +45,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.ForwardedHeaderFilter;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.FrameworkServlet;
......@@ -56,6 +59,7 @@ import static org.mockito.Mockito.verify;
* @author Phillip Webb
* @author Stephane Nicoll
* @author Raheela Aslam
* @author Madhura Bhave
*/
public class ServletWebServerFactoryAutoConfigurationTests {
......@@ -186,6 +190,24 @@ public class ServletWebServerFactoryAutoConfigurationTests {
});
}
@Test
public void forwardedHeaderFilterShouldBeConfigured() {
this.contextRunner.withPropertyValues("server.forward-headers-strategy=framework")
.run((context) -> {
assertThat(context).hasSingleBean(FilterRegistrationBean.class);
Filter filter = context.getBean(FilterRegistrationBean.class)
.getFilter();
assertThat(filter).isInstanceOf(ForwardedHeaderFilter.class);
});
}
@Test
public void forwardedHeaderFilterWhenStrategyNotFilterShouldNotBeConfigured() {
this.contextRunner.withPropertyValues("server.forward-headers-strategy=native")
.run((context) -> assertThat(context)
.doesNotHaveBean(FilterRegistrationBean.class));
}
private ContextConsumer<AssertableWebApplicationContext> verifyContext() {
return this::verifyContext;
}
......
......@@ -969,11 +969,11 @@ construct links to itself.
If the proxy adds conventional `X-Forwarded-For` and `X-Forwarded-Proto` headers (most
proxy servers do so), the absolute links should be rendered correctly, provided
`server.use-forward-headers` is set to `true` in your `application.properties`.
`server.forward-headers-strategy` is set to `NATIVE` or `FRAMEWORK` in your `application.properties`.
NOTE: If your application runs in Cloud Foundry or Heroku, the
`server.use-forward-headers` property defaults to `true`. In all
other instances, it defaults to `false`.
`server.forward-headers-strategy` property defaults to `NATIVE`. In all
other instances, it defaults to `NONE`.
......@@ -1006,7 +1006,7 @@ NOTE: You can trust all proxies by setting the `internal-proxies` to empty (but
so in production).
You can take complete control of the configuration of Tomcat's `RemoteIpValve` by
switching the automatic one off (to do so, set `server.use-forward-headers=false`) and
switching the automatic one off (to do so, set `server.forward-headers-strategy=NONE`) and
adding a new valve instance in a `TomcatServletWebServerFactory` bean.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment