[Spring] Client IP 는 어떻게 가져오는 걸까

Hocaron·2023년 9월 26일
9

Spring

목록 보기
33/44
post-thumbnail

우리 서버로 오기까지의 많은 IP 들을 거치지만, 우리는 최초 IP(Client IP) 가 필요하다.
저번 포스팅에서 살펴본 헤더값에서 해당 데이터를 가져올 수 있는데, 스프링은 X-Forwarded-For 에서 어떻게 Client IP 를 가져오는지 알아보자.

Client IP 가져오는 방법

❎ X-Forwarded-For 헤더에 직접 접근하여 가져오는 방법

String ipAddresses = request.getHeader("x-forwarded-for");
String ipAddress = Arrays.stream(ipAddresses.split(","))  // Client IP
            .findFirst()
            .orElse("");

X-Forwarded-For 헤더는 위변조가 가능하기 때문에 무조건 제일 앞에 ip 를 조회하는 것은 위험하다.

✅ HttpServletRequest의 getRemoteAddr() 이 클라이언트 IP를 전달하도록 스프링 설정을 하는 방법

yml 에 필요한 설정

server:
  forward-headers-strategy: native

remoteAddr 을 통해 Client IP 를 가져올 수 있다

String ipAddress = (HttpServletRequest) request.getRemoteAddr(); // Client IP IP

remoteAddr 을 통해 Client IP 를 어떻게 가져오는지 코드로 살펴보자

임베디드 웹 서버 종류에는 tomcat, jetty, undertow 등등 다양하게 있지만 tomcat 을 사용한다고 가정하고 구현체 코드를 살펴볼 것이다.

server:
  forward-headers-strategy: native

native 로 설정한 경우를 먼저 보자.

forward-headers-strategy: native 설정이 동작

customizeRemoteIpValve() 가 호출되면서, getOrDeduceUseForwardHeaders() 가 실행된다.

native 로 설정하였으므로,getOrDeduceUseForwardHeaders() 가 true 로 반환된다.

RemoteIpValve 객체 생성

if 내부 로직을 타게 되면서, RemoteIpValve 객체가 생성된다.

Client Ip 조회

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

internalProxies(사설 IP) 정규 표현식

    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");
  • 10.x.x.x
  • 192.168.x.x
  • 169.254.x.x
  • ...

remoteAddr 에 조회한 Client Ip Set

조회한 클라이언트 IP 를 request.setRemoteAddr(remoteIp) 에서 세팅해준다.
그러므로 request.getRemoteAddr() 에서 클라이언트 IP 를 가져올 수 있다.

🖐 여기서 잠깐

다른 헤더값이 추가로 필요하다면?

server:
  forward-headers-strategy: framework

framework 로 설정 시, 표준 헤더 외에 비표준 헤더를 가져올 수 있다.

설정처리할 수 있는 X-Forwarded 헤더 종류
none
nativeX-Forwarded-For, X-Forwarded-By, X-Forwarded-Proto
frameworkX-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Port, X-Forwarded-Host, X-Forwarded-Prefix


@ConditionalOnProperty 을 통해 framework 인 경우, ForwardedHeaderFilter를 bean 으로 등록한다.

ForwardedHeaderFilter 는 위와 같이 X-Forwarded-...를 추가한다.

internalProxies 외에 추가적인 프록시 Ip 설정이 필요하다면?

SpringBoot 3.x

server.tomcat.remoteip.trusted-proxies를 통해서 정규표현식으로 설정이 가능하다.

SpringBoot 2.x

ServerProperties 를 통해서 위 설정값이 설정 불가능하기 때문에, server.tomcat.remoteip.internal-proxies 에 기존 내부 IP 와 추가적인 프록시 Ip 정규표현식을 함께 사용하여 덮어씌어주는 방법이 있다.
위 방법은 기존 내부 IP에 값을 덮어씌어주는 방법이므로, 휴먼 에러 발생 위험이 높아서 나는 RemoteAddr 이 프록시 Ip 이면 그 전 Ip를 가져오는 방법으로 구현하였다.

정리

  • X-Forwarded-For 만 사용하는 경우는 native 설정으로 충분하다.
  • RemoteIpValve 에서 X-Forwarded 헤더값을 처리한다.

References

profile
기록을 통한 성장을

1개의 댓글

comment-user-thumbnail
2024년 8월 18일

거쳐온 IP 하나씩 분리해야 하나 하고 있었는데... 간단한 설정으로 해결이 가능했네요.
좋은 글 감사합니다~

답글 달기