ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Spring Boot] Real Remote(Client) IP Address 얻기
    개발기록 2022. 10. 11. 16:11
    반응형
    728x90

     

    개요

    유저의 IP 주소를 얻기위해 구글링 해보면 대부분 아래와 같은 코드를 볼 수 있을 것이다.

    이를 봤을 때 if문이 계속 반복된다는 점이 거슬렸고 리팩토링해서 내 코드에 적용해야겠다고 생각했다.

     

    참고: https://jul-liet.tistory.com/202

     

    구글링 시 흔히 보이는 코드

    프록시 서버 또는 로드 밸런서 등을 통과하면서 원 IP 주소를 식별하는 Header가 달라질 수 있다.

    X-Forwarded-For가 표준 Header로 쓰이고 있지만, 이 외에도 앞서 말한 이유로 인해 Header가 다를 수도 있으니 대표적인 Header들에 몇몇에 대해서 request를 체크할 필요가 있다.

     

    아래 코드들에 보이는 "X-Forwarded-For", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR" 들이 대표적으로 체크해볼 Header 들이다.

     

    cf) 대표적인 Header들을 체크해도 임의로 어딘가(proxy server 등)에서 Header에 대해 임의의 key, value를 설정하여,

    원 IP 주소를 못 찾을 수가 있는데, 사수님 말로는 이를 "프록시 헬"이라고 한다고 한다.

     

    ※ cf) 프록시 서버(proxy server):
    프록시 서버는 클라이언트가 자신을 통해서 다른 네트워크 서비스에 간접적으로 접속할 수 있게 해 주는 컴퓨터 시스템이나 응용 프로그램을 가리킨다. 서버와 클라이언트 사이에 중계기로서 대리로 통신을 수행하는 것을 가리켜 '프록시', 그 중계 기능을 하는 것을 프록시 서버라고 부른다.

    출처: https://ko.wikipedia.org/wiki/%ED%94%84%EB%A1%9D%EC%8B%9C_%EC%84%9C%EB%B2%84

     

    public static String getClientIP(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        logger.info("X-FORWARDED-FOR : " + ip);
    
        if (ip == null) {
            ip = request.getHeader("Proxy-Client-IP");
            logger.info("Proxy-Client-IP : " + ip);
        }
        if (ip == null) {
            ip = request.getHeader("WL-Proxy-Client-IP");
            logger.info("WL-Proxy-Client-IP : " + ip);
        }
        if (ip == null) {
            ip = request.getHeader("HTTP_CLIENT_IP");
            logger.info("HTTP_CLIENT_IP : " + ip);
        }
        if (ip == null) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
            logger.info("HTTP_X_FORWARDED_FOR : " + ip);
        }
        if (ip == null) {
            ip = request.getRemoteAddr();
            logger.info("getRemoteAddr : "+ip);
        }
        logger.info("Result : IP Address : "+ip);
    
        return ip;
    }

     

    리팩토링 및 내 코드에 적용

    1. 리팩토링

    header 종류들을 배열로 선언한다.

    이후 ip address를 담을 변수로 ip를 선언한다.

     

    private String[] headerTypes = {"X-Forwarded-For", "Proxy-Client-IP",
    		"WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"};
    private String ip;

     

    향상된 for문을 통해 header 종류마다 ip 주소가 담겨있는 지 체크한다.

     

    for(String headerType: headerTypes) {
        ip = request.getHeader(headerType);
        if(ip != null) break;
    }

     

    headerTypes 배열에 담긴 header 종류 모두에 ip 주소가 없을 경우,

    request.getRemoteAddr()을 통해 remote IP address를 얻어온다.

     

    if (ip == null) ip = request.getRemoteAddr();
    log.info("Real Remote(Client) IP Address: " + ip);

     

    2. 내 코드에 적용

    requset 로그 찍는 부분에 해당 코드를 적용해서 IP 주소도 로그로 남기도록 코드를 적용했다.

    코드를 적용하고 api를 호출하면 IP주소가 0:0:0:0:0:0:0:1로 찍힌다.

    이는 IPv6으로 IPv4의 127.0.0.1과 똑같다.

    IntelliJ에서 IPv4로 찍히게 하는 방법은 아래 주소를 참고하자.

     

    IPv4 설정 참고: https://gaemi606.tistory.com/entry/IntelliJ-IPv4-%EC%84%A4%EC%A0%95

     

    package com.??.interceptor;
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.??.filter.CachingResponseWrapper;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.io.IOUtils;
    import org.springframework.stereotype.Component;
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.util.ContentCachingRequestWrapper;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.InputStream;
    import java.io.StringWriter;
    
    @Slf4j
    @Component
    public class HttpLogInterceptor implements HandlerInterceptor {
        private final ObjectMapper objectMapper;
        
        // 적용
        private String[] headerTypes = {"X-Forwarded-For", "Proxy-Client-IP",
                                        "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"};
        private String ip;
    
        public HttpLogInterceptor(ObjectMapper objectMapper) {
            this.objectMapper = objectMapper;
        }
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
            if (request instanceof ContentCachingRequestWrapper) {
                log.info("uri: " + request.getRequestURI());
            }
    
        // 적용
            for(String headerType: headerTypes) {
                ip = request.getHeader(headerType);
                if(ip != null) break;
            }
    		
            // 적용
            if (ip == null) ip = request.getRemoteAddr();
            log.info("Real Remote(Client) IP Address: " + ip);
    
            return true;
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
                throws Exception {
    
            InputStream tmpIs = ((CachingResponseWrapper) response).getContentInputStream();
            StringWriter writer = new StringWriter();
            IOUtils.copy(tmpIs, writer, "UTF-8");
            String res = writer.toString();
            log.info("response - {}", res);
        }
    }

     

     

     

    728x90
Designed by Tistory.