Spring Framework

Spring RequestContextHolder으로 Client IP 주소 가져오기

Junuuu 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