Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -562,8 +562,9 @@ server:

NOTE: You can trust all proxies by setting the `internal-proxies` to empty (but do not do so in production).

TIP: If you are using Tomcat and terminating SSL at the proxy, configprop:server.tomcat.redirect-context-root[] should be set to `false`.
TIP: If you are using Tomcat, terminating SSL at the proxy, and have set configprop:server.tomcat.use-relative-redirects[] to `false`, then configprop:server.tomcat.redirect-context-root[] should also be set to `false`.
This allows the `X-Forwarded-Proto` header to be honored before any redirects are performed.
When relative redirects are in use, which is Tomcat's default, the context root redirect carries no scheme so there is nothing for the header to correct.

You can take complete control of the configuration of Tomcat's javadoc:org.apache.catalina.valves.RemoteIpValve[] by switching the automatic one off (to do so, set `server.forward-headers-strategy=NONE`) and adding a new valve instance using a javadoc:org.springframework.boot.web.server.WebServerFactoryCustomizer[] bean.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
* @author Florian Storz
* @author Michael Weidmann
* @author Lasse Wulff
* @author Tiziano Basile
* @since 4.0.0
*/
@ConfigurationProperties("server.tomcat")
Expand Down Expand Up @@ -103,9 +104,11 @@ public class TomcatServerProperties {

/**
* Whether HTTP 1.1 and later location headers generated by a call to sendRedirect
* will use relative or absolute redirects.
* will use relative or absolute redirects. When not set, Tomcat's own default is
* used, which is relative unless strict servlet compliance is enabled. Has no effect
* on a reactive web server.
*/
private boolean useRelativeRedirects;
private @Nullable Boolean useRelativeRedirects;

/**
* Character encoding to use to decode the URI.
Expand Down Expand Up @@ -235,11 +238,11 @@ public void setRedirectContextRoot(Boolean redirectContextRoot) {
this.redirectContextRoot = redirectContextRoot;
}

public boolean isUseRelativeRedirects() {
public @Nullable Boolean getUseRelativeRedirects() {
return this.useRelativeRedirects;
}

public void setUseRelativeRedirects(boolean useRelativeRedirects) {
public void setUseRelativeRedirects(@Nullable Boolean useRelativeRedirects) {
this.useRelativeRedirects = useRelativeRedirects;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
*
* @author Brian Clozel
* @author Phillip Webb
* @author Tiziano Basile
*/
class TomcatServletWebServerFactoryCustomizer
implements WebServerFactoryCustomizer<TomcatServletWebServerFactory>, Ordered {
Expand All @@ -52,7 +53,9 @@ public void customize(TomcatServletWebServerFactory factory) {
if (this.tomcatProperties.getRedirectContextRoot() != null) {
customizeRedirectContextRoot(factory, this.tomcatProperties.getRedirectContextRoot());
}
customizeUseRelativeRedirects(factory, this.tomcatProperties.isUseRelativeRedirects());
if (this.tomcatProperties.getUseRelativeRedirects() != null) {
customizeUseRelativeRedirects(factory, this.tomcatProperties.getUseRelativeRedirects());
}
}

private void customizeRedirectContextRoot(ConfigurableTomcatWebServerFactory factory, boolean redirectContextRoot) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
* Tests for {@link TomcatServerProperties}.
*
* @author Andy Wilkinson
* @author Tiziano Basile
*/
class TomcatServerPropertiesTests {

Expand Down Expand Up @@ -95,7 +96,7 @@ void testTomcatBinding() {
assertThat(this.properties.getBackgroundProcessorDelay()).hasSeconds(10);
assertThat(this.properties.getRelaxedPathChars()).containsExactly('|', '<');
assertThat(this.properties.getRelaxedQueryChars()).containsExactly('^', '|');
assertThat(this.properties.isUseRelativeRedirects()).isTrue();
assertThat(this.properties.getUseRelativeRedirects()).isTrue();
}

@Test
Expand Down Expand Up @@ -235,8 +236,8 @@ void tomcatInternalProxiesMatchesDefault() {
}

@Test
void tomcatUseRelativeRedirectsDefaultsToFalse() {
assertThat(this.properties.isUseRelativeRedirects()).isFalse();
void tomcatUseRelativeRedirectsIsNotSetByDefault() {
assertThat(this.properties.getUseRelativeRedirects()).isNull();
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.springframework.boot.tomcat.autoconfigure.servlet;

import org.apache.catalina.Context;
import org.apache.catalina.core.StandardContext;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

Expand All @@ -30,11 +31,16 @@
import org.springframework.test.context.support.TestPropertySourceUtils;

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 TomcatServletWebServerFactoryCustomizer}.
*
* @author Phillip Webb
* @author Tiziano Basile
*/
class TomcatServletWebServerFactoryCustomizerTests {

Expand Down Expand Up @@ -80,14 +86,39 @@ void redirectContextRootCanBeConfigured() {
}

@Test
void useRelativeRedirectsCanBeConfigured() {
void useRelativeRedirectsWhenNotSetDoesNotCustomizeContext() {
TomcatServletWebServerFactory factory = customizeAndGetFactory();
Context context = mock(Context.class);
factory.getContextCustomizers().forEach((customizer) -> customizer.customize(context));
then(context).should(never()).setUseRelativeRedirects(anyBoolean());
}

@Test
void useRelativeRedirectsWhenNotSetUsesTomcatsDefault() {
assertThat(this.tomcatProperties.getUseRelativeRedirects()).isNull();
TomcatWebServer server = customizeAndGetServer();
Context context = (Context) server.getTomcat().getHost().findChildren()[0];
assertThat(context.getUseRelativeRedirects()).isEqualTo(new StandardContext().getUseRelativeRedirects());
}

@Test
void useRelativeRedirectsCanBeEnabled() {
bind("server.tomcat.use-relative-redirects=true");
assertThat(this.tomcatProperties.isUseRelativeRedirects()).isTrue();
assertThat(this.tomcatProperties.getUseRelativeRedirects()).isTrue();
TomcatWebServer server = customizeAndGetServer();
Context context = (Context) server.getTomcat().getHost().findChildren()[0];
assertThat(context.getUseRelativeRedirects()).isTrue();
}

@Test
void useRelativeRedirectsCanBeDisabled() {
bind("server.tomcat.use-relative-redirects=false");
assertThat(this.tomcatProperties.getUseRelativeRedirects()).isFalse();
TomcatWebServer server = customizeAndGetServer();
Context context = (Context) server.getTomcat().getHost().findChildren()[0];
assertThat(context.getUseRelativeRedirects()).isFalse();
}

private void bind(String... inlinedProperties) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, inlinedProperties);
new Binder(ConfigurationPropertySources.get(this.environment)).bind("server.tomcat",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ void authServerMetadataShouldAllowAccess() {
void anonymousShouldRedirectToLogin() {
RestTestClient.ResponseSpec response = nonFollowingRedirect().get().uri("/").exchange();
response.expectStatus().isFound();
response.expectHeader().location("http://localhost:" + this.port + "/login");
response.expectHeader().location("/login");
}

@Test
Expand Down Expand Up @@ -181,7 +181,7 @@ void anonymousTokenRequestWithAcceptHeaderTextHtmlShouldRedirectToLogin() {
.body(body)
.exchange();
response.expectStatus().isFound();
response.expectHeader().location("http://localhost:" + this.port + "/login");
response.expectHeader().location("/login");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ private RestTestClient nonFollowingRedirect() {
void everythingShouldRedirectToLogin() {
RestTestClient.ResponseSpec response = nonFollowingRedirect().get().uri("/").exchange();
response.expectStatus().isFound();
response.expectHeader().location("http://localhost:" + this.port + "/login");
response.expectHeader().location("/login");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ private RestTestClient nonFollowingRedirect() {
void everythingShouldRedirectToLogin() {
RestTestClient.ResponseSpec response = nonFollowingRedirect().get().uri("/").exchange();
response.expectStatus().isFound();
response.expectHeader().location("http://localhost:" + this.port + "/login");
response.expectHeader().location("/login");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureRestTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.test.web.servlet.client.RestTestClient;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
Expand All @@ -38,9 +37,6 @@
@AutoConfigureRestTestClient
class SampleGroovyTemplateApplicationTests {

@LocalServerPort
private int port;

@Autowired
private RestTestClient restTestClient;

Expand All @@ -62,7 +58,7 @@ void testCreate() {
.body(map)
.exchange()
.expectHeader()
.value("Location", (location) -> assertThat(location).contains("localhost:" + this.port));
.value("Location", (location) -> assertThat(location).matches("/\\d+(;jsessionid=[\\w.]+)?"));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ void testLogin() {
assertThat(result.getStatus()).isEqualTo(HttpStatus.FOUND);
URI location = result.getResponseHeaders().getLocation();
assertThat(location).isNotNull();
assertThat(location.toString()).endsWith(this.port + "/");
assertThat(location.toString()).isEqualTo("/");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ void testHome() {
assertThat(result.getStatus()).isEqualTo(HttpStatus.FOUND);
URI location = result.getResponseHeaders().getLocation();
assertThat(location).isNotNull();
assertThat(location.toString()).endsWith(this.port + "/login");
assertThat(location.toString()).isEqualTo("/login");
}

@Test
Expand Down Expand Up @@ -99,7 +99,7 @@ void testLogin() {
assertThat(result.getStatus()).isEqualTo(HttpStatus.FOUND);
URI location = result.getResponseHeaders().getLocation();
assertThat(location).isNotNull();
assertThat(location.toString()).endsWith(this.port + "/");
assertThat(location.toString()).isEqualTo("/");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ void testHome() {
assertThat(result.getStatus()).isEqualTo(HttpStatus.FOUND);
URI location = result.getResponseHeaders().getLocation();
assertThat(location).isNotNull();
assertThat(location.toString()).endsWith(this.port + "/login");
assertThat(location.toString()).isEqualTo("/login");
}

@Test
Expand Down Expand Up @@ -99,7 +99,7 @@ void testLogin() {
assertThat(result.getStatus()).isEqualTo(HttpStatus.FOUND);
URI location = result.getResponseHeaders().getLocation();
assertThat(location).isNotNull();
assertThat(location.toString()).endsWith(this.port + "/");
assertThat(location.toString()).isEqualTo("/");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ void testHome() {
assertThat(result.getStatus()).isEqualTo(HttpStatus.FOUND);
URI location = result.getResponseHeaders().getLocation();
assertThat(location).isNotNull();
assertThat(location.toString()).endsWith(this.port + "/login");
assertThat(location.toString()).isEqualTo("/login");
}

@Test
Expand Down Expand Up @@ -106,7 +106,7 @@ void testLogin() {
assertThat(result.getStatus()).isEqualTo(HttpStatus.FOUND);
URI location = result.getResponseHeaders().getLocation();
assertThat(location).isNotNull();
assertThat(location.toString()).endsWith(this.port + "/");
assertThat(location.toString()).isEqualTo("/");
}

@org.springframework.boot.test.context.TestConfiguration(proxyBeanMethods = false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureRestTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.test.web.servlet.client.RestTestClient;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
Expand All @@ -43,9 +42,6 @@ class SampleWebUiApplicationTests {
@Autowired
private RestTestClient restTestClient;

@LocalServerPort
private int port;

@Test
void testHome() {
this.restTestClient.get().uri("/").exchangeSuccessfully().expectBody(String.class).value((body) -> {
Expand All @@ -67,7 +63,7 @@ void testCreate() {
.getResponseHeaders()
.getLocation();
assertThat(location).isNotNull();
assertThat(location.toString()).contains("localhost:" + this.port);
assertThat(location.toString()).matches("/\\d+(;jsessionid=[\\w.]+)?");
}

}