X-Forwared-For 에서 Client IP 얻기
Spring Boot3 에서 Reactive Web 이 아닌 그냥 Web 을 한다고 하면 Servlet 기반이고, Spring Boot 앱을 실행했을 경우 기본적으로 Embedded Tomcat 이 구동된다.
만일 다음과 같은 환경이라고 가정해 보자.
Client —-> AWS ALB ——> Nginx —–> Spring Boot3 (Embedded Tomca)
‘어느 미친놈이 저런 구조로 아키를 설계하고 구현하냐?’ 라고 비아냥될 수도 있지만 실제로 이렇게 하는 곳이 있다. 이런 구조에서 Spring Boot3 개발자은 다음과 같은 요구사항을 인프라에 전달 한다.
Real Ip 를 가지고 올 수 있게 해주세요.
Spring Boot3 에서 Real Ip, 그러니까 Client 의 IP 를 가지고 오는 방법으로 다음과 같은 코드를 자주 사용한다.
1 2 |
HttpServletRequest req = ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest(); ip = req.getRemoteAddr(); |
이렇게 해서 돌려보면 ip 에는 Nginx 의 ip를 가지고 오게 된다. 이를 해결하기 위한 다양한 방법이 존재한다.
Nginx 설정
Nginx 에서 Real IP 를 가지고 오기 위해서는 Nginx 에 자체 변수인 $remote_addr 에 Client IP 를 넣어줘야 한다. 여기서 ‘$remote_addr 변수에 값을 넣어줘야’ 말이 중요하다.
Nginx 에서도 앞에 수많은 Proxy 가 있을 경우에 $remote_addr 은 이전 Proxy 의 IP 주소가 된다.
Client —-> AWS ALB ——> Nginx
만일 위와같은 구조일 경우에 Nginx 에 $remote_addr 값은 AWS ALB 의 값이 된다.
이 문제를 해결하기 위해서 Nginx 에 ‘–with-http_realip_module’ 을 가지고 있을 경우에 특정 헤더에서 Real Ip 를 찾을 수 있도록 해준다. AWS ALB 의 경우에는 X-Forwarded-For 헤더에 Client IP 를 보존해 준다. 따라서 Nginx 에서는 다음과 같이 설정해줌으로써 $remote_addr 에 Client IP를 넣을 수 있다.
1 2 3 4 |
set_real_ip_from 172.10.10.0/24; # ALB A zone set_real_ip_from 172.10.20.0/24; # ALB C zone real_ip_header X-Forwarded-For; real_ip_recursive on; |
X-Forwarded-For 라는 헤더에서 Client IP 를 찾아야 하는데, set_real_ip_from 에 있는 IP 는 그냥 신뢰할 수 있는 IP 로 정의하고 Client IP 에서 제외된다. 이렇게 하는 이유는 X-Forwarded-For 가 다음과 같은 구조를 가지기 때문이다.
X-Forwarded-For: 213.13.24.10, 172.10.10.34
신뢰할 수 있는 IP 라는 건 Proxied IP 를 말하는 것으로 중간에 있는 서버들을 말한다. 이러한 신뢰할 수 있는 IP 가 X-Forwarded-For 에 있기 때문에 이것은 Client IP가 될 수 없다. 이 신뢰할 수 있는 IP 를 제외하고 나면 Client IP 만 남게 되고 이것을 $remote_addr 변수에 담게 된다.
이렇게하면 끝났으면 좋을련만, Nginx 뒤에 있는 Spring Boot3 에 req.getRemoteAdd() 함수에는 여전히 Client IP가 잡히지 않는다. Nginx 에 $remote_addr 은 어디까지나 Nginx 내에서만 활용되는 것이고 뒷단에 연결된 서버까지 연향을 주지 않는다.
이 경우에는 별도의 Header 를 정의해서 그 Header 값을 가지고 옴으로써 Client IP를 얻을 수 있다. 먼저 Nginx 에서 Header 값을 지정해줘야 한다. 다음과 같이 proxy_set_header 를 이용해 X-Real-IP 에 $remote_addr 값을 담아 준다.
1 |
proxy_set_header X-Real-IP $remote_addr; |
이렇게 하면 다음과 같은 코드로 Client IP 를 가지고 올 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
private String[] headerTypes = {"X-Real-IP", "X-Forwarded-For", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"}; private String ip; @GetMapping(value = "/getIp") public String getIp() { HttpServletRequest req = ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest(); for(String headerType: headerTypes) { ip = req.getHeader(headerType); if(ip != null) break; } if (ip == null) ip = req.getRemoteAddr(); log.info("Real Remote(Client) IP Address: " + ip); return ip; } |
Nginx 에서 설정을 하게 되면 결국 Spring Boot3 에서 Nginx 에서 넘겨주는 Client IP 를 담고 있는 별도의 Header 값을 가지고 와야 하는 코드를 작성해야 한다.
Spring Boot3 에 Tomcat 설정
원래 Tomcat 에서는 Valve 를 이용해서 Real IP 를 가지고 올 수 있다. 놀랍게도 이 방법은 Nginx 의 설정과 방법과 동일하고 문법만 다르다. 신뢰할 수 있는 IP 대역을 지정하고 체크할 Header 를 지정해 주면 된다. 이것을 Spring Boot3 에서는 application.properties 에 설정만으로 간단하게 할 수 있다.
다음과 같다.
1 2 3 4 5 6 7 8 9 |
server.forward-headers-strategy: native server.tomcat.remoteip.remote-ip-header=x-forwarded-for server.tomcat.remoteip.internal-proxies=10\.\d{1,3}\.\d{1,3}\.\d{1,3}|\ 169\.254\.\d{1,3}\.\d{1,3}|\ 127\.\d{1,3}\.\d{1,3}\.\d{1,3}|\ 172\.1[6-9]{1}\.\d{1,3}\.\d{1,3}|\ 172\.2[0-9]{1}\.\d{1,3}\.\d{1,3}|\ 172\.3[0-1]{1}\.\d{1,3}\.\d{1,3}|\ 192\.168\.96\.78 |
internal-proxies 는 신뢰할 IP 설정이라고 보면 되고, Nginx 에서 set_real_ip_from 설정이며, remote-ip-header 는 어느 헤더에서 찾을지를 지정하는 것으로 Nginx 에서 real_ip_header 와 같다.
이렇게 하면 Tomcat 은 Servlet 객체에 Client Ip 를 담아주게 되고 req.getRemoteAddr() 호출만으로, 추가적인 코딩 없이 Client Ip 를 가지고 올 수 있게 된다.