diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/how-to/pages/webserver.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/how-to/pages/webserver.adoc index e61358d17c2..4536cd27766 100644 --- a/documentation/spring-boot-docs/src/docs/antora/modules/how-to/pages/webserver.adoc +++ b/documentation/spring-boot-docs/src/docs/antora/modules/how-to/pages/webserver.adoc @@ -517,8 +517,8 @@ If your web server of choice supports the set of HTTP headers you need, setting | xref:how-to:webserver.adoc#howto.webserver.use-behind-a-proxy-server.tomcat[] | Jetty -| `"X-Forwarded-*"` -| +| `"X-Forwarded-*"`, `"Forwarded"` +| xref:how-to:webserver.adoc#howto.webserver.use-behind-a-proxy-server.jetty[] | Reactor Netty | `"X-Forwarded-*"` @@ -569,6 +569,24 @@ You can take complete control of the configuration of Tomcat's javadoc:org.apach +[[howto.webserver.use-behind-a-proxy-server.jetty]] +=== Customize Jetty's Proxy Configuration + +If you use Jetty, you can additionally configure the type of the headers used to carry "`forwarded`" information. +By default, the `"X-Forwarded-*"` format is used, but you can opt into the standard RFC variant: + +[configprops,yaml] +---- +server: + jetty: + forwarded-headers: + header-format: "standard" +---- + +For more specific options, you can switch off the forwarded configuration (using `server.forward-headers-strategy=NONE`) and directly use Jetty's javadoc:org.eclipse.jetty.server.ForwardedRequestCustomizer[] to change the HTTP configuration. + + + [[howto.webserver.enable-multiple-connectors]] == Enable Multiple Connectors diff --git a/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/ConfigurableJettyWebServerFactory.java b/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/ConfigurableJettyWebServerFactory.java index 4e794b0ed94..804f9fe0276 100644 --- a/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/ConfigurableJettyWebServerFactory.java +++ b/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/ConfigurableJettyWebServerFactory.java @@ -56,6 +56,15 @@ public interface ConfigurableJettyWebServerFactory extends ConfigurableWebServer */ void setUseForwardHeaders(boolean useForwardHeaders); + /** + * Set if the RFC forwarded header should be processed. + *

+ * {@link #setUseForwardHeaders(boolean)} will take precedence if enabled. + * @param useRfcForwardHeader if forwarded header should be used + * @since 4.2.0 + */ + void setUseRfcForwardHeader(boolean useRfcForwardHeader); + /** * Add {@link JettyServerCustomizer}s that will be applied to the {@link Server} * before it is started. diff --git a/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/ForwardHeadersCustomizer.java b/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/ForwardHeadersCustomizer.java index c1394ddcd06..2016719d028 100644 --- a/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/ForwardHeadersCustomizer.java +++ b/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/ForwardHeadersCustomizer.java @@ -30,9 +30,36 @@ import org.eclipse.jetty.server.Server; */ public class ForwardHeadersCustomizer implements JettyServerCustomizer { + private final boolean useXForwarded; + + /** + * Create a new customizer instance. + * @param useXForwarded "X-Forwarded-*" headers are used if {@code true}, otherwise + * standard "Forwarded" are used instead. + * @since 4.2.0 + */ + public ForwardHeadersCustomizer(boolean useXForwarded) { + this.useXForwarded = useXForwarded; + } + + /** + * Create a new customizer instance with "X-Forwarded-*" support. + */ + public ForwardHeadersCustomizer() { + this(true); + } + @Override public void customize(Server server) { ForwardedRequestCustomizer customizer = new ForwardedRequestCustomizer(); + if (this.useXForwarded) { + // disable "Forwarded" support + customizer.setForwardedHeader(null); + } + else { + // disable "X-Forwarded-*" support + customizer.setForwardedOnly(true); + } for (Connector connector : server.getConnectors()) { for (ConnectionFactory connectionFactory : connector.getConnectionFactories()) { if (connectionFactory instanceof HttpConfiguration.ConnectionFactory jettyConnectionFactory) { diff --git a/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/JettyWebServerFactory.java b/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/JettyWebServerFactory.java index 13c569f995d..37829f4afc8 100644 --- a/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/JettyWebServerFactory.java +++ b/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/JettyWebServerFactory.java @@ -64,6 +64,8 @@ public class JettyWebServerFactory extends AbstractConfigurableWebServerFactory private boolean useForwardHeaders; + private boolean useRfcForwardHeader; + private Set jettyServerCustomizers = new LinkedHashSet<>(); private int maxConnections = -1; @@ -180,6 +182,15 @@ public class JettyWebServerFactory extends AbstractConfigurableWebServerFactory this.useForwardHeaders = useForwardHeaders; } + public boolean isUseRfcForwardHeader() { + return this.useRfcForwardHeader; + } + + @Override + public void setUseRfcForwardHeader(boolean useRfcForwardHeader) { + this.useRfcForwardHeader = useRfcForwardHeader; + } + protected AbstractConnector createConnector(InetSocketAddress address, Server server) { return this.createConnector(address, server, null, null, null); } diff --git a/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/autoconfigure/JettyServerProperties.java b/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/autoconfigure/JettyServerProperties.java index cf6ab922c91..dba86721121 100644 --- a/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/autoconfigure/JettyServerProperties.java +++ b/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/autoconfigure/JettyServerProperties.java @@ -86,6 +86,11 @@ public class JettyServerProperties { */ private final Accesslog accesslog = new Accesslog(); + /** + * Forwarded headers configuration. + */ + private final Forwardedheaders forwardedHeaders = new Forwardedheaders(); + /** * Thread related configuration. */ @@ -95,6 +100,10 @@ public class JettyServerProperties { return this.accesslog; } + public Forwardedheaders getForwardedHeaders() { + return this.forwardedHeaders; + } + public Threads getThreads() { return this.threads; } @@ -268,6 +277,43 @@ public class JettyServerProperties { } + /** + * Forwarded headers. + */ + public static class Forwardedheaders { + + /** + * Format of the forwarded headers to support. + */ + private HeaderFormat headerFormat = HeaderFormat.X_FORWARDED; + + public HeaderFormat getHeaderFormat() { + return this.headerFormat; + } + + public void setHeaderFormat(HeaderFormat headerFormat) { + this.headerFormat = headerFormat; + } + + /** + * Formats of forwarded headers supported by {@link Forwardedheaders}. + */ + public enum HeaderFormat { + + /** + * Use the standard "Forwarded" header, as defined by RFC 7239. + */ + STANDARD, + + /** + * Use the non-standard "X-Forwarded-*" headers. + */ + X_FORWARDED + + } + + } + /** * Jetty thread properties. */ diff --git a/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/autoconfigure/JettyWebServerFactoryCustomizer.java b/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/autoconfigure/JettyWebServerFactoryCustomizer.java index 8088cd35481..7e4e6127ca9 100644 --- a/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/autoconfigure/JettyWebServerFactoryCustomizer.java +++ b/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/autoconfigure/JettyWebServerFactoryCustomizer.java @@ -79,7 +79,9 @@ public class JettyWebServerFactoryCustomizer @Override public void customize(ConfigurableJettyWebServerFactory factory) { - factory.setUseForwardHeaders(getOrDeduceUseForwardHeaders()); + if (getOrDeduceUseForwardHeaders()) { + configureForwardedSupport(factory); + } JettyServerProperties.Threads threadProperties = this.jettyProperties.getThreads(); factory.setThreadPool(JettyThreadPool.create(this.jettyProperties.getThreads())); PropertyMapper map = PropertyMapper.get(); @@ -121,6 +123,13 @@ public class JettyWebServerFactoryCustomizer return this.serverProperties.getForwardHeadersStrategy().equals(ServerProperties.ForwardHeadersStrategy.NATIVE); } + private void configureForwardedSupport(ConfigurableJettyWebServerFactory factory) { + switch (this.jettyProperties.getForwardedHeaders().getHeaderFormat()) { + case X_FORWARDED -> factory.setUseForwardHeaders(true); + case STANDARD -> factory.setUseRfcForwardHeader(true); + } + } + private Consumer customizeHttpConfigurations(ConfigurableJettyWebServerFactory factory, BiConsumer action) { return customizeConnectionFactories(factory, HttpConfiguration.ConnectionFactory.class, diff --git a/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/reactive/JettyReactiveWebServerFactory.java b/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/reactive/JettyReactiveWebServerFactory.java index 82ee17f02c3..5018d4e7d7f 100644 --- a/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/reactive/JettyReactiveWebServerFactory.java +++ b/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/reactive/JettyReactiveWebServerFactory.java @@ -118,7 +118,10 @@ public class JettyReactiveWebServerFactory extends JettyWebServerFactory customizer.customize(server); } if (this.isUseForwardHeaders()) { - new ForwardHeadersCustomizer().customize(server); + new ForwardHeadersCustomizer(true).customize(server); + } + else if (this.isUseRfcForwardHeader()) { + new ForwardHeadersCustomizer(false).customize(server); } if (getShutdown() == Shutdown.GRACEFUL) { GracefulHandler gracefulHandler = new GracefulHandler(); diff --git a/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/servlet/JettyServletWebServerFactory.java b/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/servlet/JettyServletWebServerFactory.java index 0dd9605395e..4a06baccb96 100644 --- a/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/servlet/JettyServletWebServerFactory.java +++ b/module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/servlet/JettyServletWebServerFactory.java @@ -177,7 +177,10 @@ public class JettyServletWebServerFactory extends JettyWebServerFactory customizer.customize(server); } if (this.isUseForwardHeaders()) { - new ForwardHeadersCustomizer().customize(server); + new ForwardHeadersCustomizer(true).customize(server); + } + else if (this.isUseRfcForwardHeader()) { + new ForwardHeadersCustomizer(false).customize(server); } if (getShutdown() == Shutdown.GRACEFUL) { GracefulHandler gracefulHandler = new GracefulHandler(); diff --git a/module/spring-boot-jetty/src/test/java/org/springframework/boot/jetty/ForwardHeadersCustomizerTests.java b/module/spring-boot-jetty/src/test/java/org/springframework/boot/jetty/ForwardHeadersCustomizerTests.java new file mode 100644 index 00000000000..4438eff2412 --- /dev/null +++ b/module/spring-boot-jetty/src/test/java/org/springframework/boot/jetty/ForwardHeadersCustomizerTests.java @@ -0,0 +1,66 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.jetty; + +import org.eclipse.jetty.server.ForwardedRequestCustomizer; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.Server; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ForwardHeadersCustomizer}. + * + * @author Brian Clozel + */ +class ForwardHeadersCustomizerTests { + + @Test + void defaultConstructorUsesXForwardedHeaders() { + ForwardedRequestCustomizer customizer = customize(new ForwardHeadersCustomizer()); + assertThat(customizer.getForwardedHeader()).isNull(); + assertThat(customizer.getForwardedForHeader()).isNotNull(); + } + + @Test + void useXForwardedTrueEnablesXForwardedAndDisablesForwarded() { + ForwardedRequestCustomizer customizer = customize(new ForwardHeadersCustomizer(true)); + assertThat(customizer.getForwardedHeader()).isNull(); + assertThat(customizer.getForwardedForHeader()).isNotNull(); + } + + @Test + void useXForwardedFalseEnablesForwardedAndDisablesXForwarded() { + ForwardedRequestCustomizer customizer = customize(new ForwardHeadersCustomizer(false)); + assertThat(customizer.getForwardedHeader()).isNotNull(); + assertThat(customizer.getForwardedForHeader()).isNull(); + } + + private ForwardedRequestCustomizer customize(ForwardHeadersCustomizer customizer) { + Server server = new Server(0); + customizer.customize(server); + HttpConfiguration httpConfiguration = ((HttpConfiguration.ConnectionFactory) server.getConnectors()[0] + .getConnectionFactories() + .stream() + .filter(HttpConfiguration.ConnectionFactory.class::isInstance) + .findFirst() + .orElseThrow()).getHttpConfiguration(); + return httpConfiguration.getCustomizer(ForwardedRequestCustomizer.class); + } + +} diff --git a/module/spring-boot-jetty/src/test/java/org/springframework/boot/jetty/autoconfigure/JettyServerPropertiesTests.java b/module/spring-boot-jetty/src/test/java/org/springframework/boot/jetty/autoconfigure/JettyServerPropertiesTests.java index 4793e758588..dccbdb36f08 100644 --- a/module/spring-boot-jetty/src/test/java/org/springframework/boot/jetty/autoconfigure/JettyServerPropertiesTests.java +++ b/module/spring-boot-jetty/src/test/java/org/springframework/boot/jetty/autoconfigure/JettyServerPropertiesTests.java @@ -100,6 +100,19 @@ class JettyServerPropertiesTests { assertThat(this.properties.getAccesslog().getIgnorePaths()).containsExactly("/a/path", "/b/path"); } + @Test + void forwardedHeadersDefaultToXForwarded() { + assertThat(this.properties.getForwardedHeaders().getHeaderFormat()) + .isEqualTo(JettyServerProperties.Forwardedheaders.HeaderFormat.X_FORWARDED); + } + + @Test + void testCustomizeJettyForwardedHeadersFormat() { + bind("server.jetty.forwarded-headers.header-format", "standard"); + assertThat(this.properties.getForwardedHeaders().getHeaderFormat()) + .isEqualTo(JettyServerProperties.Forwardedheaders.HeaderFormat.STANDARD); + } + @Test void jettyThreadPoolPropertyDefaultsShouldMatchServerDefault() { JettyServletWebServerFactory jettyFactory = new JettyServletWebServerFactory(0); diff --git a/module/spring-boot-jetty/src/test/java/org/springframework/boot/jetty/autoconfigure/JettyWebServerFactoryCustomizerTests.java b/module/spring-boot-jetty/src/test/java/org/springframework/boot/jetty/autoconfigure/JettyWebServerFactoryCustomizerTests.java index 14a9c85a614..79fbb7d5f60 100644 --- a/module/spring-boot-jetty/src/test/java/org/springframework/boot/jetty/autoconfigure/JettyWebServerFactoryCustomizerTests.java +++ b/module/spring-boot-jetty/src/test/java/org/springframework/boot/jetty/autoconfigure/JettyWebServerFactoryCustomizerTests.java @@ -54,8 +54,10 @@ import org.springframework.test.context.support.TestPropertySourceUtils; import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; /** * Tests for {@link JettyWebServerFactoryCustomizer}. @@ -94,7 +96,8 @@ class JettyWebServerFactoryCustomizerTests { void defaultUseForwardHeaders() { ConfigurableJettyWebServerFactory factory = mock(ConfigurableJettyWebServerFactory.class); this.customizer.customize(factory); - then(factory).should().setUseForwardHeaders(false); + then(factory).should(never()).setUseForwardHeaders(anyBoolean()); + then(factory).should(never()).setUseRfcForwardHeader(anyBoolean()); } @Test @@ -111,7 +114,28 @@ class JettyWebServerFactoryCustomizerTests { this.serverProperties.setForwardHeadersStrategy(ServerProperties.ForwardHeadersStrategy.NONE); ConfigurableJettyWebServerFactory factory = mock(ConfigurableJettyWebServerFactory.class); this.customizer.customize(factory); - then(factory).should().setUseForwardHeaders(false); + then(factory).should(never()).setUseForwardHeaders(anyBoolean()); + then(factory).should(never()).setUseRfcForwardHeader(anyBoolean()); + } + + @Test + void forwardedHeadersFormatXForwardedConfiguresUseForwardHeaders() { + this.environment.setProperty("DYNO", "-"); + bind("server.jetty.forwarded-headers.header-format=x_forwarded"); + ConfigurableJettyWebServerFactory factory = mock(ConfigurableJettyWebServerFactory.class); + this.customizer.customize(factory); + then(factory).should().setUseForwardHeaders(true); + then(factory).should(never()).setUseRfcForwardHeader(anyBoolean()); + } + + @Test + void forwardedHeadersFormatStandardConfiguresUseRfcForwardHeader() { + this.environment.setProperty("DYNO", "-"); + bind("server.jetty.forwarded-headers.header-format=standard"); + ConfigurableJettyWebServerFactory factory = mock(ConfigurableJettyWebServerFactory.class); + this.customizer.customize(factory); + then(factory).should().setUseRfcForwardHeader(true); + then(factory).should(never()).setUseForwardHeaders(anyBoolean()); } @Test diff --git a/module/spring-boot-jetty/src/test/java/org/springframework/boot/jetty/reactive/JettyReactiveWebServerFactoryTests.java b/module/spring-boot-jetty/src/test/java/org/springframework/boot/jetty/reactive/JettyReactiveWebServerFactoryTests.java index 3948dc53dcd..ecc431ef87d 100644 --- a/module/spring-boot-jetty/src/test/java/org/springframework/boot/jetty/reactive/JettyReactiveWebServerFactoryTests.java +++ b/module/spring-boot-jetty/src/test/java/org/springframework/boot/jetty/reactive/JettyReactiveWebServerFactoryTests.java @@ -43,6 +43,7 @@ import org.springframework.http.server.reactive.HttpHandler; import org.springframework.web.reactive.function.client.WebClient; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.mockito.ArgumentMatchers.any; @@ -121,6 +122,22 @@ class JettyReactiveWebServerFactoryTests extends AbstractReactiveWebServerFactor assertForwardHeaderIsUsed(factory); } + @Test + void useRfcForwardHeader() { + JettyReactiveWebServerFactory factory = getFactory(); + factory.setUseRfcForwardHeader(true); + assertRfcForwardHeaderIsUsed(factory); + } + + @Test + void useForwardHeadersTakesPrecedenceOverUseRfcForwardHeader() { + JettyReactiveWebServerFactory factory = getFactory(); + factory.setUseForwardHeaders(true); + factory.setUseRfcForwardHeader(true); + assertForwardHeaderIsUsed(factory); + assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertRfcForwardHeaderIsUsed(factory)); + } + @Test void whenServerIsShuttingDownGracefullyThenNewConnectionsCannotBeMade() { JettyReactiveWebServerFactory factory = getFactory(); diff --git a/module/spring-boot-jetty/src/test/java/org/springframework/boot/jetty/servlet/JettyServletWebServerFactoryTests.java b/module/spring-boot-jetty/src/test/java/org/springframework/boot/jetty/servlet/JettyServletWebServerFactoryTests.java index 7fe513d3253..57a3ff7f76b 100644 --- a/module/spring-boot-jetty/src/test/java/org/springframework/boot/jetty/servlet/JettyServletWebServerFactoryTests.java +++ b/module/spring-boot-jetty/src/test/java/org/springframework/boot/jetty/servlet/JettyServletWebServerFactoryTests.java @@ -442,6 +442,22 @@ class JettyServletWebServerFactoryTests extends AbstractServletWebServerFactoryT assertForwardHeaderIsUsed(factory); } + @Test + void useRfcForwardHeader() throws Exception { + JettyServletWebServerFactory factory = getFactory(); + factory.setUseRfcForwardHeader(true); + assertRfcForwardHeaderIsUsed(factory); + } + + @Test + void useForwardHeadersTakesPrecedenceOverUseRfcForwardHeader() throws Exception { + JettyServletWebServerFactory factory = getFactory(); + factory.setUseForwardHeaders(true); + factory.setUseRfcForwardHeader(true); + assertForwardHeaderIsUsed(factory); + assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertRfcForwardHeaderIsUsed(factory)); + } + @Test void defaultThreadPool() { JettyServletWebServerFactory factory = getFactory(); diff --git a/module/spring-boot-web-server/src/testFixtures/java/org/springframework/boot/web/server/reactive/AbstractReactiveWebServerFactoryTests.java b/module/spring-boot-web-server/src/testFixtures/java/org/springframework/boot/web/server/reactive/AbstractReactiveWebServerFactoryTests.java index ff6a636ce8b..47ed49cd5b8 100644 --- a/module/spring-boot-web-server/src/testFixtures/java/org/springframework/boot/web/server/reactive/AbstractReactiveWebServerFactoryTests.java +++ b/module/spring-boot-web-server/src/testFixtures/java/org/springframework/boot/web/server/reactive/AbstractReactiveWebServerFactoryTests.java @@ -677,6 +677,18 @@ public abstract class AbstractReactiveWebServerFactoryTests { assertThat(body).isEqualTo("https"); } + protected void assertRfcForwardHeaderIsUsed(ConfigurableReactiveWebServerFactory factory) { + this.webServer = factory.getWebServer(new XForwardedHandler()); + this.webServer.start(); + String body = getWebClient(this.webServer.getPort()).build() + .get() + .header("Forwarded", "proto=https") + .retrieve() + .bodyToMono(String.class) + .block(Duration.ofSeconds(30)); + assertThat(body).isEqualTo("https"); + } + private T doWithRetry(Callable action) throws Exception { Exception lastFailure = null; for (int i = 0; i < 10; i++) { diff --git a/module/spring-boot-web-server/src/testFixtures/java/org/springframework/boot/web/server/servlet/AbstractServletWebServerFactoryTests.java b/module/spring-boot-web-server/src/testFixtures/java/org/springframework/boot/web/server/servlet/AbstractServletWebServerFactoryTests.java index c841c7de887..78d6f0f8f5e 100644 --- a/module/spring-boot-web-server/src/testFixtures/java/org/springframework/boot/web/server/servlet/AbstractServletWebServerFactoryTests.java +++ b/module/spring-boot-web-server/src/testFixtures/java/org/springframework/boot/web/server/servlet/AbstractServletWebServerFactoryTests.java @@ -1617,6 +1617,14 @@ public abstract class AbstractServletWebServerFactoryTests { .contains("remoteaddr=140.211.11.130"); } + protected void assertRfcForwardHeaderIsUsed(ServletWebServerFactory factory) + throws IOException, URISyntaxException { + this.webServer = factory.getWebServer(new ServletRegistrationBean<>(new ExampleServlet(true, false), "/hello")); + this.webServer.start(); + assertThat(getResponse(getLocalUrl("/hello"), "Forwarded:for=140.211.11.130")) + .contains("remoteaddr=140.211.11.130"); + } + protected abstract ConfigurableServletWebServerFactory getFactory(); protected abstract org.apache.jasper.servlet.@Nullable JspServlet getJspServlet() throws Exception;