본문으로 건너뛰기
개발 머꼬
개발 노트Spring
hohyeon.dev2

nginx 뒤에 둔 Spring 앱에서 소셜 로그인 redirect_uri가 http로 만들어진 이유

  • #Auth
  • #Engineering Note
  • #Spring
  • #Spring Security

문제 발생

HTTPS는 nginx에서 끝내고, Spring Boot 앱에는 http://127.0.0.1:8080으로 넘기는 흔한 구성이었습니다. 여기에 Spring Security의 OAuth2 로그인을 붙이고, 구글 콘솔에는 https://example.com/login/oauth2/code/google을 등록해 뒀습니다.

그런데 로그인 버튼을 누르자 구글이 등록된 주소와 다르다며 막았습니다. 이동한 주소를 열어 보니 redirect_uri가 http://127.0.0.1:8080/login/oauth2/code/google로 들어가 있었습니다. 로컬에서는 멀쩡했으니 더 당황스러웠습니다.

원인 분석

redirect-uri의 기본값은 앱이 받은 요청 주소로 만들어집니다. Spring Security 문서에 따르면 기본 redirect URI 템플릿은 {baseUrl}/login/oauth2/code/{registrationId}이고, {baseUrl}은 {baseScheme}://{baseHost}{basePort}{basePath}로 풀립니다. 앱이 받은 요청이 http://127.0.0.1:8080이었으니 그 주소 그대로 만들어진 겁니다.

앱은 앞에 nginx가 있다는 걸 모릅니다. Spring Security 문서의 프록시 설정 절이 바로 이 상황을 예로 듭니다 — 로드 밸런서가 https://example.com/ 요청을 내부 서버로 넘길 때, 제대로 설정하지 않으면 애플리케이션 서버는 로드 밸런서가 있는 줄 모르고 클라이언트가 내부 주소를 직접 요청한 것처럼 처리한다고요.

그리고 Spring Boot는 기본으로 전달 헤더를 읽지 않습니다. Spring Boot 문서에 따르면 server.forward-headers-strategy는 지원되는 클라우드 플랫폼에서 돌 때만 NATIVE가 기본이고, 그 밖의 경우는 NONE이 기본입니다. 직접 띄운 서버라면 nginx가 X-Forwarded-Proto를 보내 줘도 앱은 그 헤더를 쳐다보지 않습니다.

해결 방안

  1. nginx가 원래 요청 정보를 넘기게 합니다. nginx가 맨 앞단이라면 클라이언트가 보낸 값을 이어 붙이지 말고 직접 덮어씁니다. 이유는 4번에 있습니다.
location / {
    proxy_pass http://127.0.0.1:8080;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $remote_addr;
}
  1. 앱이 그 헤더를 읽게 합니다. 문서에 따르면 프록시가 X-Forwarded-For와 X-Forwarded-Proto를 붙여 준다면 NATIVE로 충분합니다. 이 값으로 부족한 경우엔 FRAMEWORK로 두면 Spring의 ForwardedHeaderFilter가 처리합니다.
server:
  forward-headers-strategy: native

Tomcat은 신뢰할 내부 프록시를 정규식으로 따로 정해 둡니다. nginx가 다른 서버에 있다면 그 주소가 server.tomcat.remoteip.internal-proxies 범위에 들어가는지도 봅니다. 문서는 이 값을 비워서 모든 프록시를 믿게 할 수 있지만 운영에서는 그러지 말라고 적어 둡니다.

  1. redirect-uri는 절대 주소로 박지 말고 템플릿으로 둡니다. 문서는 redirect-uri를 URI 템플릿 변수로 설정하는 게 프록시 뒤에서 특히 유용하다고 말합니다. 그래야 주소를 펼칠 때 X-Forwarded-* 헤더가 쓰인다고요. 환경마다 주소를 따로 적을 필요도 없어집니다.

  2. 바깥에서 들어온 전달 헤더는 믿지 않습니다. 이 헤더들은 프록시가 덮어쓰지 않는 한 클라이언트가 보낸 값 그대로입니다. Spring Security 문서는 앞에 신뢰할 프록시 없이 이 헤더를 믿으면 요청이 다른 호스트나 프로토콜로 들어온 것처럼 속을 수 있다고 경고하고, 네트워크 가장자리의 프록시가 바깥에서 온 전달 헤더를 지우거나 덮어써야 한다고 말합니다. Forwarded와 X-Forwarded-* 둘 다요.

  3. 확인은 로그인 버튼을 누른 직후 주소로 합니다. 구글로 넘어가는 주소의 redirect_uri 값이 https://example.com/...인지 봅니다. 로컬에서는 이 문제가 절대 안 보이니, 프록시를 거치는 스테이징에서 한 번은 직접 눌러 봐야 합니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.