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!