mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-09-17 12:09:16 +00:00
Refine Forwarded headers configuration for Reactor servers
This commit introduces new properties for configuring the HTTP headers that should be used for forward support. The new `server.netty.forwarded-headers.header-format` property allows "standard" or "x_forwarded" to select "Forwarded" or "X-Forwarded-*" headers. This aligns the behavior of Netty server with the "FRAMEWORK" strategy. Closes gh-51031
This commit is contained in:
+24
-1
@@ -59,6 +59,8 @@ public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
|
||||
|
||||
private boolean useForwardHeaders;
|
||||
|
||||
private boolean useRfcForwardHeader;
|
||||
|
||||
private @Nullable ReactorResourceFactory resourceFactory;
|
||||
|
||||
public NettyReactiveWebServerFactory() {
|
||||
@@ -139,6 +141,17 @@ public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
|
||||
this.useForwardHeaders = useForwardHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if the RFC "Forwarded" header should be processed.
|
||||
* <p>
|
||||
* {@link #setUseForwardHeaders(boolean)} will take precedence if enabled.
|
||||
* @param useRfcForwardHeader if the "Forwarded" header should be used
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public void setUseRfcForwardHeader(boolean useRfcForwardHeader) {
|
||||
this.useRfcForwardHeader = useRfcForwardHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ReactorResourceFactory} to get the shared resources from.
|
||||
* @param resourceFactory the server resources
|
||||
@@ -157,10 +170,20 @@ public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFact
|
||||
CompressionCustomizer compressionCustomizer = new CompressionCustomizer(getCompression());
|
||||
server = compressionCustomizer.apply(server);
|
||||
}
|
||||
server = server.protocol(listProtocols()).forwarded(this.useForwardHeaders);
|
||||
server = configureForwardedHeaders(server.protocol(listProtocols()));
|
||||
return applyCustomizers(server);
|
||||
}
|
||||
|
||||
private HttpServer configureForwardedHeaders(HttpServer server) {
|
||||
if (this.useForwardHeaders) {
|
||||
return server.forwarded(false, false);
|
||||
}
|
||||
if (this.useRfcForwardHeader) {
|
||||
return server.forwarded(true, false);
|
||||
}
|
||||
return server.noForwarded();
|
||||
}
|
||||
|
||||
private HttpServer customizeSslConfiguration(HttpServer httpServer, Ssl ssl) {
|
||||
SslServerCustomizer customizer = new SslServerCustomizer(getHttp2(), ssl.getClientAuth(), getSslBundle(),
|
||||
getServerNameSslBundles());
|
||||
|
||||
+10
-1
@@ -59,7 +59,9 @@ public class NettyReactiveWebServerFactoryCustomizer
|
||||
|
||||
@Override
|
||||
public void customize(NettyReactiveWebServerFactory factory) {
|
||||
factory.setUseForwardHeaders(getOrDeduceUseForwardHeaders());
|
||||
if (getOrDeduceUseForwardHeaders()) {
|
||||
configureForwardedSupport(factory);
|
||||
}
|
||||
PropertyMapper map = PropertyMapper.get();
|
||||
map.from(this.nettyProperties::getConnectionTimeout)
|
||||
.to((connectionTimeout) -> customizeConnectionTimeout(factory, connectionTimeout));
|
||||
@@ -81,6 +83,13 @@ public class NettyReactiveWebServerFactoryCustomizer
|
||||
return this.serverProperties.getForwardHeadersStrategy().equals(ServerProperties.ForwardHeadersStrategy.NATIVE);
|
||||
}
|
||||
|
||||
private void configureForwardedSupport(NettyReactiveWebServerFactory factory) {
|
||||
switch (this.nettyProperties.getForwardedHeaders().getHeaderFormat()) {
|
||||
case X_FORWARDED -> factory.setUseForwardHeaders(true);
|
||||
case STANDARD -> factory.setUseRfcForwardHeader(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void customizeConnectionTimeout(NettyReactiveWebServerFactory factory, Duration connectionTimeout) {
|
||||
factory.addServerCustomizers((httpServer) -> httpServer.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,
|
||||
(int) connectionTimeout.toMillis()));
|
||||
|
||||
+46
@@ -90,6 +90,11 @@ public class NettyServerProperties {
|
||||
*/
|
||||
private @Nullable Duration idleTimeout;
|
||||
|
||||
/**
|
||||
* Forwarded headers configuration.
|
||||
*/
|
||||
private final Forwardedheaders forwardedHeaders = new Forwardedheaders();
|
||||
|
||||
public @Nullable Duration getConnectionTimeout() {
|
||||
return this.connectionTimeout;
|
||||
}
|
||||
@@ -146,4 +151,45 @@ public class NettyServerProperties {
|
||||
this.idleTimeout = idleTimeout;
|
||||
}
|
||||
|
||||
public Forwardedheaders getForwardedHeaders() {
|
||||
return this.forwardedHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+16
@@ -146,6 +146,22 @@ class NettyReactiveWebServerFactoryTests extends AbstractReactiveWebServerFactor
|
||||
assertForwardHeaderIsUsed(factory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void useRfcForwardHeader() {
|
||||
NettyReactiveWebServerFactory factory = getFactory();
|
||||
factory.setUseRfcForwardHeader(true);
|
||||
assertRfcForwardHeaderIsUsed(factory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void useForwardHeadersTakesPrecedenceOverUseRfcForwardHeader() {
|
||||
NettyReactiveWebServerFactory factory = getFactory();
|
||||
factory.setUseForwardHeaders(true);
|
||||
factory.setUseRfcForwardHeader(true);
|
||||
assertForwardHeaderIsUsed(factory);
|
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertRfcForwardHeaderIsUsed(factory));
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithPackageResources("test.jks")
|
||||
void whenSslIsConfiguredWithAValidAliasARequestSucceeds() {
|
||||
|
||||
+27
-2
@@ -39,6 +39,7 @@ import org.springframework.util.unit.DataSize;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
@@ -85,7 +86,8 @@ class NettyReactiveWebServerFactoryCustomizerTests {
|
||||
void defaultUseForwardHeaders() {
|
||||
NettyReactiveWebServerFactory factory = mock(NettyReactiveWebServerFactory.class);
|
||||
this.customizer.customize(factory);
|
||||
then(factory).should().setUseForwardHeaders(false);
|
||||
then(factory).should(never()).setUseForwardHeaders(anyBoolean());
|
||||
then(factory).should(never()).setUseRfcForwardHeader(anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -102,7 +104,30 @@ class NettyReactiveWebServerFactoryCustomizerTests {
|
||||
this.serverProperties.setForwardHeadersStrategy(ServerProperties.ForwardHeadersStrategy.NONE);
|
||||
NettyReactiveWebServerFactory factory = mock(NettyReactiveWebServerFactory.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", "-");
|
||||
this.nettyProperties.getForwardedHeaders()
|
||||
.setHeaderFormat(NettyServerProperties.Forwardedheaders.HeaderFormat.X_FORWARDED);
|
||||
NettyReactiveWebServerFactory factory = mock(NettyReactiveWebServerFactory.class);
|
||||
this.customizer.customize(factory);
|
||||
then(factory).should().setUseForwardHeaders(true);
|
||||
then(factory).should(never()).setUseRfcForwardHeader(anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
void forwardedHeadersFormatStandardConfiguresUseRfcForwardHeader() {
|
||||
this.environment.setProperty("DYNO", "-");
|
||||
this.nettyProperties.getForwardedHeaders()
|
||||
.setHeaderFormat(NettyServerProperties.Forwardedheaders.HeaderFormat.STANDARD);
|
||||
NettyReactiveWebServerFactory factory = mock(NettyReactiveWebServerFactory.class);
|
||||
this.customizer.customize(factory);
|
||||
then(factory).should().setUseRfcForwardHeader(true);
|
||||
then(factory).should(never()).setUseForwardHeaders(anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+13
@@ -73,6 +73,19 @@ class NettyServerPropertiesTests {
|
||||
.isEqualTo(HttpDecoderSpec.DEFAULT_INITIAL_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void forwardedHeadersDefaultToXForwarded() {
|
||||
assertThat(this.properties.getForwardedHeaders().getHeaderFormat())
|
||||
.isEqualTo(NettyServerProperties.Forwardedheaders.HeaderFormat.X_FORWARDED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCustomizeNettyForwardedHeadersFormat() {
|
||||
bind("server.netty.forwarded-headers.header-format", "standard");
|
||||
assertThat(this.properties.getForwardedHeaders().getHeaderFormat())
|
||||
.isEqualTo(NettyServerProperties.Forwardedheaders.HeaderFormat.STANDARD);
|
||||
}
|
||||
|
||||
private void bind(String name, String value) {
|
||||
bind(Collections.singletonMap(name, value));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user