nginx 뒤에서 Spring이 요청 스킴을 http로 읽는다
앞선 세션 문제를 고치고 나서 바로 이어 터진 건이다. 증상만 보면 전혀 달라 보였는데 뿌리는 같았다 — 리버스 프록시 뒤에서 서버가 자기가 어디 있는지 모른다.
증상
비밀번호 재설정 메일 링크가
http://sync.skkil.org/...로 나간다. HTTPS여야 한다.OAuth 리다이렉트 URI가
http로 만들어져 provider 쪽에서 거부당한다.secure: true로 준 쿠키가 간헐적으로 무시된다.
셋 다 코드에서 스킴을 하드코딩한 곳은 없었다.
원인
nginx가 TLS를 종료하고 서버에는 평문 HTTP로 넘긴다. 서버 입장에서 들어오는 요청은 진짜로 HTTP다.
브라우저 --HTTPS--> nginx --HTTP--> Spring Boot
ServletUriComponentsBuilder.fromCurrentRequest()나 request.isSecure()는 눈앞의 요청을 그대로 믿기 때문에 전부 http, false가 된다. 원래 스킴은 X-Forwarded-Proto 헤더에만 남아 있는데, Spring은 기본적으로 이 헤더를 신뢰하지 않는다. 아무나 위조할 수 있는 헤더라 기본값이 무시인 게 맞다.
해결
nginx가 헤더를 보내고,
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
Spring이 그 헤더를 신뢰하도록 켠다.
server:
forward-headers-strategy: framework
framework는 Spring의 ForwardedHeaderFilter를 등록해 요청 객체 자체를 원본 스킴·호스트로 교정한다. 이후로는 fromCurrentRequest()도, isSecure()도, 쿠키 Secure 판정도 전부 알아서 맞는다. 애플리케이션 코드는 한 줄도 안 고쳤다.
주의
이 설정은 프록시 뒤에 있을 때만 켜야 한다. 서버가 인터넷에 직접 노출된 상태에서 켜면 아무나 X-Forwarded-Proto: https를 보내 서버를 속일 수 있다. 우리는 nginx를 반드시 거치는 구조라 안전하다.
로컬에는 프록시가 없으므로 이 옵션을 프로덕션 프로파일에만 뒀다. 또 하나의 로컬-프로덕션 차이가 늘어난 셈이라, 이번엔 차이 목록 문서에 바로 적었다.
댓글
아직 댓글이 없습니다.