우리 서버로 오기까지의 많은 IP 들을 거치지만, 우리는 최초 IP(Client IP) 가 필요하다.
저번 포스팅에서 살펴본 헤더값에서 해당 데이터를 가져올 수 있는데, 스프링은 X-Forwarded-For
에서 어떻게 Client IP 를 가져오는지 알아보자.
String ipAddresses = request.getHeader("x-forwarded-for");
String ipAddress = Arrays.stream(ipAddresses.split(",")) // Client IP
.findFirst()
.orElse("");
X-Forwarded-For
헤더는 위변조가 가능하기 때문에 무조건 제일 앞에 ip 를 조회하는 것은 위험하다.
server:
forward-headers-strategy: native
String ipAddress = (HttpServletRequest) request.getRemoteAddr(); // Client IP IP
임베디드 웹 서버 종류에는 tomcat, jetty, undertow 등등 다양하게 있지만 tomcat 을 사용한다고 가정하고 구현체 코드를 살펴볼 것이다.
server:
forward-headers-strategy: native
native 로 설정한 경우를 먼저 보자.
customizeRemoteIpValve() 가 호출되면서, getOrDeduceUseForwardHeaders() 가 실행된다.
native 로 설정하였으므로,getOrDeduceUseForwardHeaders()
가 true 로 반환된다.
if 내부 로직을 타게 되면서, RemoteIpValve 객체가 생성된다.
invoke() 에서, 배열 remoteIpHeaderValue
에서 for 문을 돌게 된다.
remoteIpHeaderValue
에 존재하는 ip 중에서 제일 오른쪽(마지막 호출 ip) 부터 해당 ip 가 internalProxies 인지 확인하고, 맞다면 하나씩 왼쪽(전에 호출된 ip) 로 idx 를 줄여간다.
X-Forwarded-For: 211.12.14.10, 10.20.11.19, 10.20.11.17
클라이언트 IP ⬅️ 사설 IP 대역 o ⬅️ 사설 IP 대역 0
private Pattern internalProxies = Pattern.compile(
"10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" +
"192\\.168\\.\\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}|" +
"0:0:0:0:0:0:0:1|::1");
조회한 클라이언트 IP 를 request.setRemoteAddr(remoteIp)
에서 세팅해준다.
그러므로 request.getRemoteAddr()
에서 클라이언트 IP 를 가져올 수 있다.
server:
forward-headers-strategy: framework
framework
로 설정 시, 표준 헤더 외에 비표준 헤더를 가져올 수 있다.
설정 | 처리할 수 있는 X-Forwarded 헤더 종류 |
---|---|
none | |
native | X-Forwarded-For, X-Forwarded-By, X-Forwarded-Proto |
framework | X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Port, X-Forwarded-Host, X-Forwarded-Prefix |
@ConditionalOnProperty
을 통해 framework 인 경우, ForwardedHeaderFilter를 bean 으로 등록한다.
ForwardedHeaderFilter
는 위와 같이 X-Forwarded-...를 추가한다.
server.tomcat.remoteip.trusted-proxies를 통해서 정규표현식으로 설정이 가능하다.
ServerProperties 를 통해서 위 설정값이 설정 불가능하기 때문에, server.tomcat.remoteip.internal-proxies
에 기존 내부 IP 와 추가적인 프록시 Ip 정규표현식을 함께 사용하여 덮어씌어주는 방법이 있다.
위 방법은 기존 내부 IP에 값을 덮어씌어주는 방법이므로, 휴먼 에러 발생 위험이 높아서 나는 RemoteAddr 이 프록시 Ip 이면 그 전 Ip를 가져오는 방법으로 구현하였다.
거쳐온 IP 하나씩 분리해야 하나 하고 있었는데... 간단한 설정으로 해결이 가능했네요.
좋은 글 감사합니다~