ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring RequestContextHolder으로 Client IP 주소 가져오기
    Spring Framework 2023. 7. 22. 00:01

    RequestContextHolder란?

    Spring 2.x부터 제공되던 기능으로 Spring 전역으로 Request에 대한 정보를 가져오고자 할 때 사용하는 유틸성 클래스입니다.

    주로 Controller가 아닌, Service에서 Request 객체를 참고하려 할 때 사용합니다.

    private static final boolean jsfPresent =
    ClassUtils.isPresent("jakarta.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());
    
    private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
    new NamedThreadLocal<>("Request attributes");
    
    private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
    new NamedInheritableThreadLocal<>("Request context");

    Servlet이 생성될 때 클래스가 초기화되며 내부 필드로는 ThreadLocal으로 값들을 가지고 있습니다.

     

    주의할 점으로는 new Thread 혹은 Executor를 사용한 ThreadPool에서의 참조 등에서는 DispatcherServlet 범위에서 벗어나서 새로운 스레드가 생성되기 때문에 RequestContextHolder에서 값을 꺼내올 수 없습니다.

     

    CurrentRequestAttributes 메서드

    쓰레드 바운더리 내에 존재하는 RequestAttributes 클래스를 가져옵니다.

     

    RequestAttributes 인터페이스

    global session이라는 optional 개념을 통해 모든 종류의 request/session 메커니즘을 구현합니다.

    특히 servlet requests에 대해 구현할 수 있습니다.

     

    ServletRequestAttributes 클래스

    RequestAttribute의 구현체로써 session, global session을 구분하지 않고 servlet request 객체에 접근할 수 있습니다.

    getRequest로 HttpServletRequest를 꺼내옵니다.

     

    HttpServletRequest 클래스

        /**
         * Returns the value of the specified request header as a <code>String</code>. If the request did not include a
         * header of the specified name, this method returns <code>null</code>. If there are multiple headers with the same
         * name, this method returns the first head in the request. The header name is case insensitive. You can use this
         * method with any request header.
         *
         * @param name a <code>String</code> specifying the header name
         *
         * @return a <code>String</code> containing the value of the requested header, or <code>null</code> if the request
         *             does not have a header of that name
         */
        String getHeader(String name);

    HTTP servlet 정보를 제공하기 위해 ServletRequest 인터페이스를 extends 하여 구현합니다.

    getHeader 메서드를 통하여 지정된 요청 헤더의 값을 문자열로 반환하고 존재하지 않는 경우 null을 반환합니다.

     

     

    예시 코드

    private fun getIpAddr(): String? {
        val req = (RequestContextHolder.currentRequestAttributes() as ServletRequestAttributes).request
        var ip = req.getHeader("X-FORWARDED-FOR")
        if (ip == null) ip = req.remoteAddr
        return ip
      }

    1. RequestContextHolder에서 현재 쓰레드의 RequestAttribtes를 꺼내온다.

    2. 구현체인 ServletRequestAttributes로 변환한다.

    3. getRequest를 통해 HttpServletRequest를 꺼내온다.

    4. X-FORWARDED_FOR을 활용하여 클라이언트의 IP주소를 꺼내온다.

    5. 만약 null이라면 HttpServletRequest.remoteAdr을 얻어와서 반환한다.

     

    X-Forwarded-For란?

    • 보통 클라이언트는 forward 또는 reverse proxy를 통해 연결되므로 서버는 최종 proxy의 IP만 알게 되어 쓸모 없어지는 경우가 많습니다.
    • 이때 request header인 X-Forwarded-For를 활용하여 de-facto(사실상 표준)으로 클라이언트의 원본 IP 주소를 구별할 수 있습니다.

    getRemoteAdr 메서드

    Returns the Internet Protocol (IP) address of the client or last proxy that sent the request. For HTTP servlets, same as the value of the CGI variable REMOTE_ADDR.
    Returns:
    a String containing the IP address of the client that sent the request
    String getRemoteAddr();

    요청을 보낸 클라이언트 또는 마지막 프록시의 IP 주소를 반환합니다.

    HTTP Servlet의 경우에는 CGI변수 REMOVE_ADDR 값과 동일합니다.

     

     

    참고자료

    https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/context/request/RequestContextHolder.html

    https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/X-Forwarded-For

    https://gompangs.tistory.com/entry/Spring-RequestContextHolder

     

    댓글

Designed by Tistory.