Merge pull request #51173 from basteez

Closes gh-51173

* gh-51173:
  Polish "Default use-relative-redirects to true"
  Default use-relative-redirects to true
This commit is contained in:
Andy Wilkinson
2026-09-07 09:18:50 +01:00
13 changed files with 37 additions and 29 deletions
@@ -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.
@@ -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.
@@ -45,6 +45,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link TomcatServerProperties}.
*
* @author Andy Wilkinson
* @author Tiziano Basile
*/
class TomcatServerPropertiesTests {
@@ -73,7 +74,7 @@ class TomcatServerPropertiesTests {
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 @@ class TomcatServerPropertiesTests {
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 @@ class TomcatServerPropertiesTests {
}
@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
@@ -35,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link TomcatServletWebServerFactoryCustomizer}.
*
* @author Phillip Webb
* @author Tiziano Basile
*/
class TomcatServletWebServerFactoryCustomizerTests {
@@ -80,14 +81,22 @@ class TomcatServletWebServerFactoryCustomizerTests {
}
@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",
@@ -113,7 +113,7 @@ class SampleOAuth2AuthorizationServerApplicationTests {
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 @@ class SampleOAuth2AuthorizationServerApplicationTests {
.body(body)
.exchange();
response.expectStatus().isFound();
response.expectHeader().location("http://localhost:" + this.port + "/login");
response.expectHeader().location("/login");
}
}
@@ -54,7 +54,7 @@ class SampleOAuth2ClientApplicationTests {
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
@@ -52,7 +52,7 @@ class SampleSaml2RelyingPartyApplicationTests {
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
@@ -22,7 +22,6 @@ import org.springframework.beans.factory.annotation.Autowired;
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 @@ import static org.assertj.core.api.Assertions.assertThat;
@AutoConfigureRestTestClient
class SampleGroovyTemplateApplicationTests {
@LocalServerPort
private int port;
@Autowired
private RestTestClient restTestClient;
@@ -62,7 +58,7 @@ class SampleGroovyTemplateApplicationTests {
.body(map)
.exchange()
.expectHeader()
.value("Location", (location) -> assertThat(location).contains("localhost:" + this.port));
.value("Location", (location) -> assertThat(location).matches("/\\d+(;jsessionid=[\\w.]+)?"));
}
@Test
@@ -91,7 +91,7 @@ class SampleMethodSecurityApplicationTests {
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
@@ -71,7 +71,7 @@ class SampleWebSecureCustomApplicationTests {
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 @@ class SampleWebSecureCustomApplicationTests {
assertThat(result.getStatus()).isEqualTo(HttpStatus.FOUND);
URI location = result.getResponseHeaders().getLocation();
assertThat(location).isNotNull();
assertThat(location.toString()).endsWith(this.port + "/");
assertThat(location.toString()).isEqualTo("/");
}
}
@@ -71,7 +71,7 @@ class SampleWebSecureJdbcApplicationTests {
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 @@ class SampleWebSecureJdbcApplicationTests {
assertThat(result.getStatus()).isEqualTo(HttpStatus.FOUND);
URI location = result.getResponseHeaders().getLocation();
assertThat(location).isNotNull();
assertThat(location.toString()).endsWith(this.port + "/");
assertThat(location.toString()).isEqualTo("/");
}
}
@@ -78,7 +78,7 @@ class SampleWebSecureApplicationTests {
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 @@ class SampleWebSecureApplicationTests {
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)
@@ -24,7 +24,6 @@ import org.springframework.beans.factory.annotation.Autowired;
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 @@ class SampleWebUiApplicationTests {
.getResponseHeaders()
.getLocation();
assertThat(location).isNotNull();
assertThat(location.toString()).contains("localhost:" + this.port);
assertThat(location.toString()).matches("/\\d+(;jsessionid=[\\w.]+)?");
}
}