For a Spring Boot webmvc application, when the property server.tomcat.use-relative-redirects
is set to true
and the server returns a 302 redirect, the Location
header should contain only the URI path, not a full URL ("/path" instead of "http://host/path").
However, if the property server.forward-headers-strategy
is also set to framework
and the HTTP request contains a "forwarded" header like "X-Forwarded-Proto: http", the Location
header contains a full URL, instead of only the URI path as expected.
Detected in versions
Spring Boot: 2.5.4, 2.4.5 Java: 11.0.6
Code Example
Controller:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class RedirectController {
@GetMapping("/redirect-to-foo")
public String getRedirect() {
return "redirect:/foo";
}
@GetMapping("/foo")
@ResponseBody
public String getFoo() {
return "This is a test";
}
}
Controller Test:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.reactive.server.WebTestClient;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWebTestClient
@TestPropertySource(properties = {
"server.tomcat.use-relative-redirects=true",
"server.forward-headers-strategy=framework"
})
class RedirectControllerTest {
@Test
void testRedirect(@Autowired WebTestClient webClient) throws Exception {
webClient.get()
.uri("/redirect-to-foo")
.exchange()
.expectStatus().is3xxRedirection()
.expectHeader().location("/foo");
// Succeeds
}
@Test
void testRedirectWithForwardedHeaders(@Autowired WebTestClient webClient) throws Exception {
webClient.get()
.uri("/redirect-to-foo")
.header("X-Forwarded-Proto", "http")
.exchange()
.expectStatus().is3xxRedirection()
.expectHeader().location("/foo");
// Fails with error:
// java.lang.AssertionError: Response header 'Location' expected:</foo> but was:<http://localhost/foo>
}
}
Comment From: wilkinsona
Thanks for the report, @merusso-regions. You should be able to work around the problem by enabling relative redirects on the filter. You can do so by adding the following bean to your application:
@Bean
@ConditionalOnProperty(value = "server.forward-headers-strategy", havingValue = "framework")
FilterRegistrationBean<ForwardedHeaderFilter> customFilter() {
ForwardedHeaderFilter filter = new ForwardedHeaderFilter();
filter.setRelativeRedirects(true);
FilterRegistrationBean<ForwardedHeaderFilter> registration = new FilterRegistrationBean<>(filter);
registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.ERROR);
registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registration;
}
We'll have to explore if it would be possible for us to do this automatically when using Tomcat with relative redirects enabled.
Comment From: merusso-regions
I can confirm that the workaround you provided does fix the issue for me. Thanks for the info!
Comment From: philwebb
Closing in favor of PR #29333 thanks @terminux!