[Java] See the behavior of relative redirects by setting server.tomcat.use-relative-redirects in Spring Boot

5 minute read

Overview

  • Verify what value the specified redirect is set in the Location header by setting server.tomcat.use-relative-redirects of Spring Boot

Verification environment

  • Spring Boot 2.3.3
  • Spring WebMVC 5.2.8
  • Apache Tomcat Embed 9.0.37
  • Java 14 (AdoptOpenJDK 14.0.2)
  • macOS Catalina

server.tomcat.use-relative-redirects setting

A setting that controls whether the HTTP 1.1 and later Location headers generated by a call to HttpServletResponse#sendRedirect use relative or absolute redirects. Can be set in application.properties etc.

server.tomcat.use-relative-redirects=false

If true is specified, the relative redirect specified in HttpServletResponse#sendRedirect is specified in Location header as it is.

When false is specified, the relative redirect specified in HttpServletResponse#sendRedirect is converted into an absolute redirect and specified in Location header.

The default value of server.tomcat.use-relative-redirects is false.

Common Application properties

Whether HTTP 1.1 and later location headers generated by a call to sendRedirect will use relative or absolute redirects.

server.tomcat.use-relative-redirects is similar to Apache Tomcat’s useRelativeRedirects setting.

Apache Tomcat 9 Configuration Reference (9.0.37) - The Context Container

Controls whether HTTP 1.1 and later location headers generated by a call to javax.servlet.http.HttpServletResponse#sendRedirect(String) will use relative or absolute redirects. Relative redirects are more efficient but may not work with reverse proxies that change the context path It should be noted that it is not recommended to use a reverse proxy to change the context path because of the multiple issues it creates.Absolute redirects should work with reverse proxies that change the context path but may cause issues with the org.apache. catalina.filters.RemoteIpFilter if the filter is changing the scheme and/or port.If the org.apache.catalina.STRICT_SERVLET_COMPLIANCE system property is set to true, the default value of this attribute will be false, else the default value will be true .

How to specify the redirect to be verified

Use redirect: prefix for view name

You can specify the redirect destination by using the redirect: prefix in the view name.

@GetMapping("/controller/relative")
public ModelAndView relative() {
  // specify relative redirect for view name
  return new ModelAndView("redirect:/controller/hello");
}

When server.tomcat.use-relative-redirects=true, relative redirects are directly specified in Location header.

When server.tomcat.use-relative-redirects=false, relative redirects are converted to absolute redirects and specified in Location header.

It seems that HttpServletResponse#sendRedirect is called inside Spring.

Spring Web MVC Servlet Stack -Documents

You can use the special redirect: prefix in the view name to perform the redirect. UrlBasedViewResolver (and its subclasses) recognize this as an instruction that needs to be redirected. The rest of the view name is the redirect URL.

The net effect is the same as if the controller returned a RedirectView, but now the controller itself can work in terms of the logical view name. Logical view names (such as redirect:/myapp/some/resource) redirect in relation to the current servlet context and names such as redirect:https://myhost.com/some/arbitrary/path redirect to an absolute URL. I will.

Please note that if your controller method is annotated with @ResponseStatus, the annotation value will take precedence over the response status set by RedirectView.

ResponseEntity.BodyBuilder#location

The redirect destination can be specified in the location method of ResponseEntity.BodyBuilder.

@GetMapping("/restcontroller/relative")
public ResponseEntity<?> relative() {
  // Specify relative redirect in the value of Location header
  return ResponseEntity.status(HttpStatus.FOUND)
    .location(URI.create("/restcontroller/hello"))
    .build();
}

Regardless of whether server.tomcat.use-relative-redirects is true or false, the specified relative redirect is directly specified in Location header.

It seems that HttpServletResponse#sendRedirect is not called inside Spring.

HttpServletResponse#sendRedirect

The redirect destination can be specified by sendRedirect method of HttpServletResponse.

@GetMapping("/restcontroller/sendRedirect/relative")
public void sendRedirectRelative(HttpServletResponse res) throws IOException {
  // specify relative redirect
  res.sendRedirect("/restcontroller/sendRedirect/hello");
}

When server.tomcat.use-relative-redirects=true, relative redirects are directly specified in Location header.

When server.tomcat.use-relative-redirects=false, relative redirects are converted to absolute redirects and specified in Location header.

Prepare two controller classes for verification

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class SampleController {

  @GetMapping("/controller/relative")
  public ModelAndView relative() {
    // specify relative redirect for view name
    return new ModelAndView("redirect:/controller/hello");
  }

  @GetMapping("/controller/absolute")
  public ModelAndView absolute() {
    // specify absolute redirect for view name
    return new ModelAndView("redirect:http://localhost:8080/controller/hello");
  }
}
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URI;

@RestController
public class SampleRestController {

  @GetMapping("/restcontroller/relative")
  public ResponseEntity<?> relative() {
    // Location ヘッダの値に相対リダイレクトを指定
    return ResponseEntity.status(HttpStatus.FOUND)
      .location(URI.create("/restcontroller/hello"))
      .build();
  }

  @GetMapping("/restcontroller/absolute")
  public ResponseEntity<?> absolute() {
    // Location ヘッダの値に絶対リダイレクトを指定
    return ResponseEntity.status(HttpStatus.FOUND)
      .location(URI.create("http://localhost:8080/restcontroller/hello"))
      .build();
  }

  @GetMapping("/restcontroller/sendRedirect/relative")
  public void sendRedirectRelative(HttpServletResponse res) throws IOException {
    // 相対リダイレクトを指定
    res.sendRedirect("/restcontroller/sendRedirect/hello");
  }

  @GetMapping("/restcontroller/sendRedirect/absolute")
  public void sendRedirectAbsolute(HttpServletResponse res) throws IOException {
    // 絶対リダイレクトを指定
    res.sendRedirect("http://localhost:8080/restcontroller/sendRedirect/hello");
  }
}

Location ヘッダの値を検証する

server.tomcat.use-relative-redirects=true の場合

ModelAndView に指定した相対リダイレクト /controller/hello がそのまま Location ヘッダに指定される。

$ curl --include http://localhost:8080/controller/relative
HTTP/1.1 302 
Location: /controller/hello
Content-Language: ja-JP
Content-Length: 0
Date: Tue, 25 Aug 2020 11:10:27 GMT

ModelAndView に指定した絶対リダイレクト http://localhost:8080/controller/hello がそのまま Location ヘッダに指定される。

$ curl --include http://localhost:8080/controller/absolute
HTTP/1.1 302 
Location: http://localhost:8080/controller/hello
Content-Language: ja-JP
Content-Length: 0
Date: Tue, 25 Aug 2020 11:10:31 GMT

ResponseEntity に指定した相対リダイレクト /restcontroller/hello がそのまま Location ヘッダに指定される。

$ curl --include http://localhost:8080/restcontroller/relative
HTTP/1.1 302 
Location: /restcontroller/hello
Content-Length: 0
Date: Tue, 25 Aug 2020 11:10:35 GMT

ResponseEntity に指定した絶対リダイレクト http://localhost:8080/restcontroller/hello がそのまま Location ヘッダに指定される。

$ curl --include http://localhost:8080/restcontroller/absolute
HTTP/1.1 302 
Location: http://localhost:8080/restcontroller/hello
Content-Length: 0
Date: Tue, 25 Aug 2020 11:10:38 GMT

HttpServletResponse#sendRedirect に指定した相対リダイレクト /restcontroller/sendRedirect/hello がそのまま Location ヘッダに指定される。

$ curl --include http://localhost:8080/restcontroller/sendRedirect/relative
HTTP/1.1 302 
Location: /restcontroller/sendRedirect/hello
Content-Length: 0
Date: Tue, 25 Aug 2020 11:10:41 GMT

HttpServletResponse#sendRedirect に指定した絶対リダイレクト http://localhost:8080/restcontroller/sendRedirect/hello がそのまま Location ヘッダに指定される。

$ curl --include http://localhost:8080/restcontroller/sendRedirect/absolute
HTTP/1.1 302 
Location: http://localhost:8080/restcontroller/sendRedirect/hello
Content-Length: 0
Date: Tue, 25 Aug 2020 11:10:45 GMT

server.tomcat.use-relative-redirects=false の場合

ModelAndView に指定した相対リダイレクト /controller/hello が絶対リダイレクト http://localhost:8080/controller/hello に変換されて Location ヘッダに指定される。

$ curl --include http://localhost:8080/controller/relative
HTTP/1.1 302 
Location: http://localhost:8080/controller/hello
Content-Language: ja-JP
Content-Length: 0
Date: Tue, 25 Aug 2020 11:09:21 GMT

ModelAndView に指定した絶対リダイレクト http://localhost:8080/controller/hello がそのまま Location ヘッダに指定される。

$ curl --include http://localhost:8080/controller/absolute
HTTP/1.1 302 
Location: http://localhost:8080/controller/hello
Content-Language: ja-JP
Content-Length: 0
Date: Tue, 25 Aug 2020 11:09:25 GMT

ResponseEntity に指定した相対リダイレクト /restcontroller/hello がそのまま Location ヘッダに指定される。

$ curl --include http://localhost:8080/restcontroller/relative
HTTP/1.1 302 
Location: /restcontroller/hello
Content-Length: 0
Date: Tue, 25 Aug 2020 11:09:29 GMT

ResponseEntity に指定した絶対リダイレクト http://localhost:8080/restcontroller/hello がそのまま Location ヘッダに指定される。

$ curl --include http://localhost:8080/restcontroller/absolute
HTTP/1.1 302 
Location: http://localhost:8080/restcontroller/hello
Content-Length: 0
Date: Tue, 25 Aug 2020 11:09:35 GMT

HttpServletResponse#sendRedirect に指定した相対リダイレクト /restcontroller/sendRedirect/hello が絶対リダイレクト http://localhost:8080/restcontroller/sendRedirect/hello に変換されて Location ヘッダに指定される。

$ curl --include http://localhost:8080/restcontroller/sendRedirect/relative
HTTP/1.1 302 
Location: http://localhost:8080/restcontroller/sendRedirect/hello
Content-Length: 0
Date: Tue, 25 Aug 2020 11:09:42 GMT

HttpServletResponse#sendRedirect に指定した絶対リダイレクト http://localhost:8080/restcontroller/sendRedirect/hello がそのまま Location ヘッダに指定される。

$ curl --include http://localhost:8080/restcontroller/sendRedirect/absolute
HTTP/1.1 302 
Location: http://localhost:8080/restcontroller/sendRedirect/hello
Content-Length: 0
Date: Tue, 25 Aug 2020 11:09:46 GMT

参考資料-[ModelAndView (Spring Framework 5.2.8.RELEASE API) - Javadoc Japanese translation](https://spring.pleiades.io/spring/docs/5.2.8.RELEASE/javadoc-(api/org/springframework/web/servlet/ModelAndView.html)