이 영역을 누르면 첫 페이지로 이동
Coding Groot 블로그의 첫 페이지로 이동

Coding Groot

페이지 맨 위로 올라가기

Container화 한 Spring Boot 3의 baseUrl을 Nginx header로 알맞게 바꿔주자!

Coding Groot

Container화 한 Spring Boot 3의 baseUrl을 Nginx header로 알맞게 바꿔주자!

  • 2025.03.04 18:15
  • Programming
글 작성자: Coding Groot

OAuth2 Redirect URI 관련 문제 발생

이번 프로젝트는 Nginx로 도커 환경으로 서버를 분리된 서버에 전달되도록 구성했습니다. 그래서 application의 {baseUrl}이 자동으로 서버 Url로 설정이 될 줄 알았는데 Nginx가 앞에 있어서 실제 유저가 접속할 때 쓰는 Url과 다른 Url이 되는 것을 알게 되었습니다. {baseUrl}이 이상하니 이 주소 기반으로 된 OAuth2 redirect-uri 설정도 문제가 생겼습니다. 배포 파이프라인을 수정할까 했는데 생각보다 깔끔하게 해결할 수 있었습니다. 이걸 해결하며 발견한 것들을 공유해보고자 합니다.

현재 서버 구성

현재 개발용과 프로덕션용으로 Spring Boot 애플리케이션 2개를 하나의 서버에 컨테이너화하여 도커로 배포하고 있습니다.
(비용을 위해 하나의 서버에 모두 띄우고 nginx로 프록시하도록 했습니다.)

환경별 서버 Container들

개발된 서버들이 이렇게 docker 컨테이너로 띄워져있습니다. 컨테이너들은 localhost만 listen하고 있고 Nginx 뒤에 있습니다.
443(프로덕션용)이나 8443(개발용)으로 온 HTTPS 요청은 먼저 Nginx에서 처리합니다. Nginx가 TLS 처리를 하고 http 패킷 형태로 header를 추가해서 컨테이너로 전달합니다.

그림으로 보면 다음과 같습니다.

HTTPS 요청의 관점으로 보는 서버 구성도

문제: 서버 설정 값

아래는 스프링 부트에서 쓰고 있는 application.properties 중 일부입니다.

spring:
  security:
    oauth2:
      client:
        registration:
          kakao:
            # 생략
            redirect-uri: "{baseUrl}/login/oauth2/code/kakao"
        provider:
          kakao:
            # 생략

여기서 문제가 되는 부분은 redirect-uri입니다. baseUrl이 환경이 컨테이너이기 때문에 Prod나 Dev Container 안의 스프링 애플리케이션은 자신의 baseUrl이 localhost:8080이라고 생각했습니다. 이걸 해결하기 위해 이리저리 문서랑 해결책을 찾아보았고 nginx를 이용해서 깔끔하게 해결할 수 있는 방법을 찾았습니다!

해결법

1. Nginx에 커스텀 헤더 설정

어쨌든 어떤 환경에서 왔는지 알려면 Nginx에서 받은 정보가 필요합니다. 그래서 Nginx에게 다음과 같이 커스텀 header를 통해 어떤 프로토콜, IP, HOST, Protocol로 왔는지 전달하도록 합니다.
그러면 컨테이너 안의 스프링부트 애플리케이션에서도 커스텀 헤더를 통해 자신이 실제로 어떤 Url로 배포되는지 알 수 있습니다.

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port

과연 그런가 테스트 해보기

nginx가 필요한 header를 붙이고 전달한 패킷은 HTTP이기 때문에 tcpdump로 간단하게 확인해볼 수 있겠다 싶어서 tcpdump를 해서 내용을 살펴봤습니다.
클라이언트에서 HTTP 요청을 이렇게 보냈습니다.

GET /api/test HTTP/1.1
Accept: */*
Host: api.xxxxx.com:8443
User-Agent: HTTPie

Nginx가 받고 스프링 서버로 전달해준 HTTP 요청은 최종적으로 다음과 같았습니다.

TCPDUMP로 Nginx로부터 온 패킷을 캡처한 결과

실제 서빙된 호스트 주소, 포트, 프로토콜을 header 값으로 확인할 수 있었고 이걸로 잘 작동한다는 것을 확인했습니다.

2. 스프링 애플리케이션 수정

이제 이 정보를 기반으로 spring application이 baseUrl을 설정하도록 하면 됩니다.
이전에 파이썬에서 비슷한 설정을 해본 적이 있어서 당연히 스프링에도 있을 것이라 생각했는데 아니나 다를까 스프링 공식 문서에 프록시 서버에 대한 가이드가 있어서 금방 해결법을 찾을 수 있었습니다.

출처: https://docs.spring.io/spring-boot/how-to/webserver.html#howto.webserver.use-behind-a-proxy-server

말고도 다른 글도 찾아 보면서 ForwardHeadersStrategy 설정을 통해 가능하다는 것을 알았습니다. 이때 X-Forwarded-* header를 처리하는 방식으로 native와 framework 두 가지 Strategy가 지원된다는 것을 알았습니다.

ForwardHeadersStrategy의 종류들

이것을 사용하려면 application.properties에 server.forward-headers-strategy 값을 설정하면 됩니다.

# native headers strategy
server.forward-headers-strategy=native # 혹시 framework


선택지 1. Native ForwardHeadersStrategy
서블릿 컨테이너(Tomcat, Jetty 등)의 기본 기능을 활용해서 X-Forwarded-* header를 처리하는 방식입니다. 성능이 좋지만 일부 비표준 header는 지원하지 않는다고 합니다.
저는 특이한 헤더명을 쓰지 않고 common한 header들로 충분했습니다. 그래서 위의 공식 문서에서 권고한대로 native로 충분해서 이 설정을 선택했습니다.

Native 방식은 application.yaml에 RemoteIpValve를 Trace 레벨로 로그를 찍도록 아래처럼 설정하면 실제로 프록시에서 설정한 헤더로 온 값들을 볼 수 있습니다.

아래처럼 설정하고

# native headers strategy logging
logging.level.org.apache.catalina.valves.RemoteIpValve=trace

다음처럼 프록시 헤더를 몇 개 설정해서 요청을 로컬에 띄워둔 서버에 보내봤습니다.

GET /login/oauth2/code/kakao HTTP/1.1
Host: groot-example-domain.com
X-Forwarded-Port: 9999
X-Forwarded-Proto: https

아래처럼 호스트와 프로토콜과 포트들이 넘어왔음을 애플리케이션에서 볼 수 있었습니다.

요청 결과


선택지 2. Framework ForwardHeadersStrategy
Spring Framework에서 제공하는 ForwardedHeaderFilter를 사용하여 헤더를 처리하는 방식입니다. 만약 비표준 헤더까지 완벽하게 지원해야 하면 좋은 선택지인 것 같습니다. 하지만 애플리케이션에서 처리되니 약간의 성능 오버헤드가 있을 수 있다고 합니다.

Spring Boot에서 헤더를 해석해서 baseUrl이 잘 적용됐는지 확인해보기

실제로 애플리케이션에서 이 프록시로 넘어온 헤더들을 기반으로 redirect-url이 잘 설정되는지 궁금했습니다.

그래서 매번 Request마다 Spring Boot에서 헤더를 해석해서 baseUrl이 잘 설정했는지 oauth redirect-url을 찍어 봤습니다.
새로운 OAuth2 인증 요청이 처리될 때마다 최종적으로 결정된 redirect 주소를 로깅하기 위해 다음과 같이 로깅용 Request Resolver를 만듭니다.

// 생략
@Slf4j
public class LoggingOAuth2AuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver {
private final OAuth2AuthorizationRequestResolver delegate;
public LoggingOAuth2AuthorizationRequestResolver(ClientRegistrationRepository repo, String authorizationRequestBaseUri) {
this.delegate = new DefaultOAuth2AuthorizationRequestResolver(repo, authorizationRequestBaseUri);
}
@Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
OAuth2AuthorizationRequest req = delegate.resolve(request);
if (req != null) {
log.info("Resolved redirect URI: {}", req.getRedirectUri()); // 실행될 때 req의 redirectUri를 로그를 남긴다.
}
return req;
}
@Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId) {
OAuth2AuthorizationRequest req = delegate.resolve(request, clientRegistrationId);
if (req != null) {
log.info("Resolved redirect URI: {}", req.getRedirectUri());
}
return req;
}
}

그리고 기존의 Security config에 등록해줍니다. 간단하게 로그만 보면 돼서 login 아래 모든 경로(/login/**)에 적용되도록 했습니다.

// 생략
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@Slf4j
public class SecurityConfig {
// 생략
@Autowired
private ClientRegistrationRepository clientRegistrationRepository;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
validateDependencies();
return http
// 생략
.oauth2Login(
oauth2 -> oauth2.authorizationEndpoint(authEndPoint ->
authEndPoint.authorizationRequestResolver(new LoggingOAuth2AuthorizationRequestResolver(clientRegistrationRepository, "/login/**")))) // 위의 resolver 클래스를 등록
// 생략
.build();
}
}

이렇게 애플리케이션 단에서 로그를 설정하고 직접 로컬에서 띄워서 X-Forwarded-* 헤더들을 조작해가며 다음과 같은 요청해봤습니다.

GET /login/oauth2/code/kakao HTTP/1.1
Host: groot-example-domain.com
X-Forwarded-Port: 9999
X-Forwarded-Proto: https
GET /login/oauth2/code/kakao HTTP/1.1
Host: test2.com
X-Forwarded-Port: 8888
X-Forwarded-Proto: http
GET /login/oauth2/code/kakao HTTP/1.1
Host: test3.com
X-Forwarded-Port: 2222
X-Forwarded-Proto: ftp

서버에 출력된 로그는 다음과 같이 잘 나왔습니다.

요청 받은 것을 가로채서 Resolver에서 찍힌 결과

이로써 실제 서버에서 헤더로 처리하고 resolve된 redirect URI를 보며 어떻게 baseUrl이 변하는지 명확히 확인할 수 있었습니다.

이제 Nginx 헤더 설정이 걸리는 부분이 있어서 보안적으로 위험한 부분은 없는 검토해볼 것 같습니다. 가능하면 변조가 불가능하게 서비스 헤더값을 하드코딩하는 것도 좋을 것 같네요 :)

반응형

댓글

댓글을 사용할 수 없습니다.

이 글 공유하기

  • 구독하기

    구독하기

  • 카카오톡

    카카오톡

  • 라인

    라인

  • 트위터

    트위터

  • Facebook

    Facebook

  • 카카오스토리

    카카오스토리

  • 밴드

    밴드

  • 네이버 블로그

    네이버 블로그

  • Pocket

    Pocket

  • Evernote

    Evernote

다른 글

  • 캐시

    캐시

    2025.03.20
  • 폰 노이만 구조의 특징

    폰 노이만 구조의 특징

    2025.03.20
  • Error installing cocoapods 해결 후, CocoaPods 업그레이드하는 법

    Error installing cocoapods 해결 후, CocoaPods 업그레이드하는 법

    2023.11.17
  • Flutter3.0 Firebase 연동하기

    Flutter3.0 Firebase 연동하기

    2023.10.05
다른 글 더 둘러보기

정보

Coding Groot 블로그의 첫 페이지로 이동

Coding Groot

  • Coding Groot의 첫 페이지로 이동

검색

메뉴

  • 홈
  • 태그
  • 방명록
  • 소개
  • 블로그 저작권

카테고리

  • 분류 전체보기 (184)
    • Git (23)
      • Git Tutorial (9)
      • Git Note (7)
      • Git Lecture (7)
    • Programming Language (1)
      • C (2)
      • C Sharp (5)
      • Java (4)
      • JavaScript (7)
      • Julia (5)
      • Python (4)
    • Programming (8)
      • Algorithm (2)
      • Compiler (5)
      • Data Structure (0)
      • Web (12)
      • NestJS (2)
    • DevOps, Infra (36)
      • Apple (6)
      • Cloud (15)
      • Database (1)
      • Network (4)
      • Linux (8)
    • Game Programming (11)
      • Unity Tutorial (5)
      • Unity Note (6)
    • Hardware Design (1)
      • Digital Circuit (1)
    • Note (49)
      • Coffee (2)
      • Retrospect (15)
      • Reading List (14)
    • Mathematics (1)

인기 글

공지사항

태그

  • Github
  • tutorial
  • javascript
  • 서평
  • aws
  • 한빛미디어
  • git
  • 회고
  • 전체 보기…

정보

Coding Groot의 Coding Groot

Coding Groot

Coding Groot

블로그 구독하기

  • 구독하기
  • RSS 피드

티스토리

  • 티스토리 홈
  • 이 블로그 관리하기
  • 글쓰기

나의 외부 링크

  • GitHub
  • SlideShare
  • 유니티 2020 수업
  • TIL Blog
  • 모도코

방문자

  • 전체 방문자
  • 오늘
  • 어제
Powered by Tistory / Kakao. Copyright © Coding Groot.

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.