스프링부트 Redirect시 Host 헤더 처리 방식

greenTea·2025년 9월 1일

스프링부트 Host 헤더 처리 방식 분석

운연환경 구성과정에서 host 헤더를 설정하여 http 요청을 날리는 경우 리다이렉트시 요청 주소가 아닌 host 헤더에 설정된 값으로 응답이 와 문제가 생겼습니다.

결국 /etc/hosts 설정을 통해 문제를 해결하였지만 제가 생각했던 실행방식과 다르기에 이를 공부해봤습니다.

스프링부트에서 리다이렉트 시 Host 헤더가 어떻게 처리되는지, 그리고 X-Forwarded-Host 헤더가 어떤 영향을 미치는지 실제 코드를 통해 확인해보겠습니다.

테스트 환경 구성

간단한 스프링 부트 프로젝트를 아래와 같이 만들어 줍니다.

package com.dono.mdmpractice;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.view.RedirectView;

import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestController
public class TestController {
	
	@GetMapping("/info")
	public String getBasicInfo(HttpServletRequest request) {
		String host = request.getHeader("Host");
		String xForwardedHost = request.getHeader("X-Forwarded-Host");
		String xForwardedProto = request.getHeader("X-Forwarded-Proto");
		String xForwardedPort = request.getHeader("X-Forwarded-Port");

		StringBuilder response = new StringBuilder();
		response.append("=== Request Header Info ===\n");
		response.append("Host: ").append(host).append("\n");
		response.append("X-Forwarded-Host: ").append(xForwardedHost).append("\n");
		response.append("X-Forwarded-Proto: ").append(xForwardedProto).append("\n");
		response.append("X-Forwarded-Port: ").append(xForwardedPort).append("\n");
		response.append("\n=== Request URL Info ===\n");
		response.append("getServerName(): ").append(request.getServerName()).append("\n");
		response.append("getServerPort(): ").append(request.getServerPort()).append("\n");
		response.append("getScheme(): ").append(request.getScheme()).append("\n");
		response.append("getRequestURL(): ").append(request.getRequestURL()).append("\n");

		log.info("Basic GET request - Host: {}, X-Forwarded-Host: {}", host, xForwardedHost);

		return response.toString();
	}
	
	@GetMapping("/redirect")
	public RedirectView testRedirect(HttpServletRequest request) {
		String host = request.getHeader("Host");
		String xForwardedHost = request.getHeader("X-Forwarded-Host");

		log.info("Redirect request - Host: {}, X-Forwarded-Host: {}", host, xForwardedHost);
		log.info("Request URL before redirect: {}", request.getRequestURL());
		log.info("getServerName(): {}, getScheme(): {}", request.getServerName(), request.getScheme());

		return new RedirectView("/redirected");
	}
	
	@GetMapping("/redirected")
	public String redirected() {
		return "Redirected successfully!";
	}
}

Host 헤더 테스트

이제 curl을 날려서 host헤더 설정이 어떠한 역할을 미치는지 확인을 해보겠습니다.

1. 기본 요청 (Host 헤더 미설정)

요청

curl --location 'http://localhost:8080/redirect'

응답
302응답이 옵니다

Response Headers
Location: http://localhost:8080/redirected
Content-Language: ko-KR
Content-Length: 0
Date: Wed, 27 Aug 2025 04:59:39 GMT
Keep-Alive: timeout=60
Connection: keep-alive

Host 헤더를 명시하지 않으면 기본적으로 요청 URL의 도메인(localhost:8080)이 사용됩니다.

2. Host 헤더 설정

요청

curl --location 'http://localhost:8080/redirect' \
--header 'Host: first.co.kr'

응답
같은 302 응답이 오지만 Location이 변경된 것을 확인할 수 있습니다.

Response Headers
Location: http://first.co.kr/redirected
Content-Language: ko-KR
Content-Length: 0
Date: Wed, 27 Aug 2025 05:01:05 GMT
Keep-Alive: timeout=60
Connection: keep-alive

Host 헤더를 설정하면 리다이렉트 Location에 해당 호스트가 반영됩니다.

3. X-Forwarded-Host 헤더 추가

요청

curl --location 'http://localhost:8080/redirect' \
--header 'Host: first.co.kr' \
--header 'X-Forwarded-Host: second.co.kr'

응답

Response Headers
Location: http://second.co.kr/redirected
Content-Language: ko-KR
Content-Length: 0
Date: Wed, 27 Aug 2025 05:02:08 GMT
Keep-Alive: timeout=60
Connection: keep-alive

X-Forwarded-Host 헤더가 존재하면 Host 헤더보다 우선순위가 높습니다.

내부 동작 원리 분석

RedirectView 처리 과정

RedirectView 클래스의 sendRedirect() 메소드를 보면 실제 리다이렉트 처리 과정을 확인할 수 있습니다.

RedirectView sendRedirect 메소드

sendRedirect()를 보면 먼저 http1.0 호환성 체크를 합니다.(기본값은 true입니다)

  • 이를 통과하게 되면 statusCode를 확인하게 되는데 해당 statusCodeRedirectView를 통해 직접 상태 코드를 지정한 경우에 처리가 됩니다.
  • 2번째 체크문인 else if (attributeStatusCode != null) 문의 경우에는 @ResponseStatus처럼 직접 상태 코드를 지정한 경웨 처리를 하게 됩니다,
  • 아무것도 설정하지 않은 경우 response.sendRedirect()를 타게 되는데 해당 메소드를 통과하면 Tomcat에서 redirect 관련 설정을 처리하게 됩니다.

Tomcat Response 처리

sendRedirect()가 호출 되면 tomcat의 Response 클래스가 호출됩니다.

톰캣의 Response 클래스의 sendRedirect() 메소드에서 setHeader를 통해 Location을 지정해줍니다.

Tomcat Response sendRedirect

핵심 로직을 보면:

if (getRequest().getCoyoteRequest().getSupportsRelativeRedirects() &&
        (context == null || context.getUseRelativeRedirects())) {
    locationUri = location;
} else {
    locationUri = toAbsolute(location);
}

여기서 두 가지 조건을 확인합니다:

  • getRequest().getCoyoteRequest().getSupportsRelativeRedirects(): 클라이언트가 상대경로를 해석할 수 있는지 판단
  • context == null || context.getUseRelativeRedirects(): 서버에서 상대경로 리다이렉트를 허용하는지 판단

실제로 디버그를 하고 돌려보면:

  • getRequest().getCoyoteRequest().getSupportsRelativeRedirects()true
  • context == null || context.getUseRelativeRedirects()false

스프링 부트 애플리케이션은 임베디드 톰캣을 사용하며, 웹 애플리케이션이 구동될 때 Context 객체는 항상 생성되기에 context != null입니다. 그리고 context.getUseRelativeRedirects()의 경우에는 명시적으로 server.tomcat.use-relative-redirects=true를 설정하지 않는 이상 기본값이 false입니다.

toAbsolute() 메소드 분석

결국 toAbsolute() 메소드로 가게 되는데, 이 메소드는 host, port 등을 조합해서 최종적인 절대 경로 location을 만들어서 반환합니다.

toAbsolute 메소드

전체 처리 순서

  1. 컨트롤러: new RedirectView("/redirected") 생성 및 반환
  2. DispatcherServlet: RedirectView 객체를 받아서 render 메서드 호출
  3. AbstractView: render 메서드에서 renderMergedOutputModel 호출
  4. RedirectView: createTargetUrl로 URL 생성, sendRedirect로 실제 리다이렉트 실행
  5. Tomcat Response: sendRedirect 받아서 최종적으로 Location 헤더 설정

프록시 환경에서의 설정

프록시나 로드밸런서 뒤에서 스프링부트 애플리케이션을 운영할 때는 다음 설정이 중요합니다:

Forward Headers 전략 설정

server.forward-headers-strategy=NATIVE

이 설정을 통해 X-Forwarded-* 헤더들을 자동으로 처리하도록 합니다. NATIVE로 설정하면 톰캣이 자체적으로 X-Forwarded 헤더를 인식하고 처리합니다. (기본값은 NONE)

상대경로 리다이렉트 허용

server.tomcat.use-relative-redirects=true

이 설정을 true로 하면 절대경로가 아닌 상대경로로 리다이렉트가 가능합니다. 프록시 환경에서는 프록시가 알아서 호스트 정보를 처리하므로 상대경로 리다이렉트가 유용할 수 있습니다. (기본값은 false입니다.)

마무리

스프링부트에서 Host 헤더와 X-Forwarded-Host 헤더는 리다이렉트 URL 생성에 직접적인 영향을 미칩니다.

실제 운영 환경에서는 server.forward-headers-strategy=NATIVE 설정을 통해 자동으로 처리하도록 하는 것이 권장되며, 필요에 따라 상대경로 리다이렉트 옵션도 고려해볼 수 있습니다.

profile
greenTea입니다.

0개의 댓글