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 b17e9b697f4c..e012ac6ddcb4 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 @@ -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. diff --git a/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/autoconfigure/TomcatServerProperties.java b/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/autoconfigure/TomcatServerProperties.java index 90c6e551820b..3b9ff9e05aa7 100644 --- a/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/autoconfigure/TomcatServerProperties.java +++ b/module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/autoconfigure/TomcatServerProperties.java @@ -105,7 +105,7 @@ public class TomcatServerProperties { * Whether HTTP 1.1 and later location headers generated by a call to sendRedirect * will use relative or absolute redirects. */ - private boolean useRelativeRedirects; + private boolean useRelativeRedirects = true; /** * Character encoding to use to decode the URI. diff --git a/module/spring-boot-tomcat/src/test/java/org/springframework/boot/tomcat/autoconfigure/TomcatServerPropertiesTests.java b/module/spring-boot-tomcat/src/test/java/org/springframework/boot/tomcat/autoconfigure/TomcatServerPropertiesTests.java index e9280187a80c..c7eee4a6fe40 100644 --- a/module/spring-boot-tomcat/src/test/java/org/springframework/boot/tomcat/autoconfigure/TomcatServerPropertiesTests.java +++ b/module/spring-boot-tomcat/src/test/java/org/springframework/boot/tomcat/autoconfigure/TomcatServerPropertiesTests.java @@ -45,6 +45,7 @@ * Tests for {@link TomcatServerProperties}. * * @author Andy Wilkinson + * @author Tiziano Basile */ class TomcatServerPropertiesTests { @@ -73,7 +74,7 @@ void testTomcatBinding() { map.put("server.tomcat.background-processor-delay", "10"); map.put("server.tomcat.relaxed-path-chars", "|,<"); map.put("server.tomcat.relaxed-query-chars", "^ , | "); - map.put("server.tomcat.use-relative-redirects", "true"); + map.put("server.tomcat.use-relative-redirects", "false"); bind(map); Accesslog accesslog = this.properties.getAccesslog(); assertThat(accesslog.getConditionIf()).isEqualTo("foo"); @@ -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.isUseRelativeRedirects()).isFalse(); } @Test @@ -235,8 +236,13 @@ void tomcatInternalProxiesMatchesDefault() { } @Test - void tomcatUseRelativeRedirectsDefaultsToFalse() { - assertThat(this.properties.isUseRelativeRedirects()).isFalse(); + void tomcatUseRelativeRedirectsDefaultsToTrue() { + assertThat(this.properties.isUseRelativeRedirects()).isTrue(); + } + + @Test + void tomcatUseRelativeRedirectsMatchesDefault() { + assertThat(this.properties.isUseRelativeRedirects()).isEqualTo(new StandardContext().getUseRelativeRedirects()); } @Test diff --git a/module/spring-boot-tomcat/src/test/java/org/springframework/boot/tomcat/autoconfigure/servlet/TomcatServletWebServerFactoryCustomizerTests.java b/module/spring-boot-tomcat/src/test/java/org/springframework/boot/tomcat/autoconfigure/servlet/TomcatServletWebServerFactoryCustomizerTests.java index 2a39dc33b444..a1ee752561af 100644 --- a/module/spring-boot-tomcat/src/test/java/org/springframework/boot/tomcat/autoconfigure/servlet/TomcatServletWebServerFactoryCustomizerTests.java +++ b/module/spring-boot-tomcat/src/test/java/org/springframework/boot/tomcat/autoconfigure/servlet/TomcatServletWebServerFactoryCustomizerTests.java @@ -35,6 +35,7 @@ * Tests for {@link TomcatServletWebServerFactoryCustomizer}. * * @author Phillip Webb + * @author Tiziano Basile */ class TomcatServletWebServerFactoryCustomizerTests { @@ -80,14 +81,22 @@ void redirectContextRootCanBeConfigured() { } @Test - void useRelativeRedirectsCanBeConfigured() { - bind("server.tomcat.use-relative-redirects=true"); + void useRelativeRedirectsDefaultsToTrue() { assertThat(this.tomcatProperties.isUseRelativeRedirects()).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.isUseRelativeRedirects()).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", diff --git a/smoke-test/spring-boot-smoke-test-oauth2-authorization-server/src/test/java/smoketest/oauth2/server/SampleOAuth2AuthorizationServerApplicationTests.java b/smoke-test/spring-boot-smoke-test-oauth2-authorization-server/src/test/java/smoketest/oauth2/server/SampleOAuth2AuthorizationServerApplicationTests.java index 3407774ccd99..3f196f9db8c0 100644 --- a/smoke-test/spring-boot-smoke-test-oauth2-authorization-server/src/test/java/smoketest/oauth2/server/SampleOAuth2AuthorizationServerApplicationTests.java +++ b/smoke-test/spring-boot-smoke-test-oauth2-authorization-server/src/test/java/smoketest/oauth2/server/SampleOAuth2AuthorizationServerApplicationTests.java @@ -113,7 +113,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 @@ -182,7 +182,7 @@ void anonymousTokenRequestWithAcceptHeaderTextHtmlShouldRedirectToLogin() { .body(body) .exchange(); response.expectStatus().isFound(); - response.expectHeader().location("http://localhost:" + this.port + "/login"); + response.expectHeader().location("/login"); } } diff --git a/smoke-test/spring-boot-smoke-test-oauth2-client/src/test/java/smoketest/oauth2/client/SampleOAuth2ClientApplicationTests.java b/smoke-test/spring-boot-smoke-test-oauth2-client/src/test/java/smoketest/oauth2/client/SampleOAuth2ClientApplicationTests.java index 8d7d8ce62fea..866b15d5641b 100644 --- a/smoke-test/spring-boot-smoke-test-oauth2-client/src/test/java/smoketest/oauth2/client/SampleOAuth2ClientApplicationTests.java +++ b/smoke-test/spring-boot-smoke-test-oauth2-client/src/test/java/smoketest/oauth2/client/SampleOAuth2ClientApplicationTests.java @@ -54,7 +54,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 diff --git a/smoke-test/spring-boot-smoke-test-saml2-service-provider/src/test/java/smoketest/saml2/serviceprovider/SampleSaml2RelyingPartyApplicationTests.java b/smoke-test/spring-boot-smoke-test-saml2-service-provider/src/test/java/smoketest/saml2/serviceprovider/SampleSaml2RelyingPartyApplicationTests.java index 837a8af8a02c..054d46472872 100644 --- a/smoke-test/spring-boot-smoke-test-saml2-service-provider/src/test/java/smoketest/saml2/serviceprovider/SampleSaml2RelyingPartyApplicationTests.java +++ b/smoke-test/spring-boot-smoke-test-saml2-service-provider/src/test/java/smoketest/saml2/serviceprovider/SampleSaml2RelyingPartyApplicationTests.java @@ -52,7 +52,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 diff --git a/smoke-test/spring-boot-smoke-test-web-groovy-templates/src/test/java/smoketest/groovytemplates/SampleGroovyTemplateApplicationTests.java b/smoke-test/spring-boot-smoke-test-web-groovy-templates/src/test/java/smoketest/groovytemplates/SampleGroovyTemplateApplicationTests.java index 141b49838557..ac26a57d60e5 100644 --- a/smoke-test/spring-boot-smoke-test-web-groovy-templates/src/test/java/smoketest/groovytemplates/SampleGroovyTemplateApplicationTests.java +++ b/smoke-test/spring-boot-smoke-test-web-groovy-templates/src/test/java/smoketest/groovytemplates/SampleGroovyTemplateApplicationTests.java @@ -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; @@ -38,9 +37,6 @@ @AutoConfigureRestTestClient class SampleGroovyTemplateApplicationTests { - @LocalServerPort - private int port; - @Autowired private RestTestClient restTestClient; @@ -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 diff --git a/smoke-test/spring-boot-smoke-test-web-method-security/src/test/java/smoketest/security/method/SampleMethodSecurityApplicationTests.java b/smoke-test/spring-boot-smoke-test-web-method-security/src/test/java/smoketest/security/method/SampleMethodSecurityApplicationTests.java index 552182ae9b0c..873bb2012507 100644 --- a/smoke-test/spring-boot-smoke-test-web-method-security/src/test/java/smoketest/security/method/SampleMethodSecurityApplicationTests.java +++ b/smoke-test/spring-boot-smoke-test-web-method-security/src/test/java/smoketest/security/method/SampleMethodSecurityApplicationTests.java @@ -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 diff --git a/smoke-test/spring-boot-smoke-test-web-secure-custom/src/test/java/smoketest/web/secure/custom/SampleWebSecureCustomApplicationTests.java b/smoke-test/spring-boot-smoke-test-web-secure-custom/src/test/java/smoketest/web/secure/custom/SampleWebSecureCustomApplicationTests.java index b16c34ab450e..565a71cd1ccf 100644 --- a/smoke-test/spring-boot-smoke-test-web-secure-custom/src/test/java/smoketest/web/secure/custom/SampleWebSecureCustomApplicationTests.java +++ b/smoke-test/spring-boot-smoke-test-web-secure-custom/src/test/java/smoketest/web/secure/custom/SampleWebSecureCustomApplicationTests.java @@ -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 @@ -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("/"); } } diff --git a/smoke-test/spring-boot-smoke-test-web-secure-jdbc/src/test/java/smoketest/web/secure/jdbc/SampleWebSecureJdbcApplicationTests.java b/smoke-test/spring-boot-smoke-test-web-secure-jdbc/src/test/java/smoketest/web/secure/jdbc/SampleWebSecureJdbcApplicationTests.java index 82ba02cf1989..799aa10a703b 100644 --- a/smoke-test/spring-boot-smoke-test-web-secure-jdbc/src/test/java/smoketest/web/secure/jdbc/SampleWebSecureJdbcApplicationTests.java +++ b/smoke-test/spring-boot-smoke-test-web-secure-jdbc/src/test/java/smoketest/web/secure/jdbc/SampleWebSecureJdbcApplicationTests.java @@ -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 @@ -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("/"); } } diff --git a/smoke-test/spring-boot-smoke-test-web-secure/src/test/java/smoketest/web/secure/SampleWebSecureApplicationTests.java b/smoke-test/spring-boot-smoke-test-web-secure/src/test/java/smoketest/web/secure/SampleWebSecureApplicationTests.java index 781d02fed73f..c31a55fa650a 100644 --- a/smoke-test/spring-boot-smoke-test-web-secure/src/test/java/smoketest/web/secure/SampleWebSecureApplicationTests.java +++ b/smoke-test/spring-boot-smoke-test-web-secure/src/test/java/smoketest/web/secure/SampleWebSecureApplicationTests.java @@ -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 @@ -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) diff --git a/smoke-test/spring-boot-smoke-test-web-thymeleaf/src/test/java/smoketest/web/thymeleaf/SampleWebUiApplicationTests.java b/smoke-test/spring-boot-smoke-test-web-thymeleaf/src/test/java/smoketest/web/thymeleaf/SampleWebUiApplicationTests.java index 8c21af9d78aa..64f8f8e5714c 100644 --- a/smoke-test/spring-boot-smoke-test-web-thymeleaf/src/test/java/smoketest/web/thymeleaf/SampleWebUiApplicationTests.java +++ b/smoke-test/spring-boot-smoke-test-web-thymeleaf/src/test/java/smoketest/web/thymeleaf/SampleWebUiApplicationTests.java @@ -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; @@ -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) -> { @@ -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.]+)?"); } }