Post

Spring MVC HandlerInterceptor

1. 스프링 MVC 핸들러

Spring 인터셉터가 작동하는 방식을 이해하기 위한 HandlerMapping을 확인한다.

HandlerMapping의 목적은 핸들러 메서드를 URL에 매핑하는 것이다. 그렇게 하면 DispatcherServlet이 요청을 처리할 때 호출할 수 있다.

실제로 DispatcherServlet은 HandlerAdapter를 사용하여 실제로 메서드를 호출한다.

요컨대 인터셉터는 요청을 가로채서 처리한다. 로깅 및 인증 확인과 같은 반복적인 핸들러 코드를 피하는데 도움이 된다.

HandlerInterceptor를 사용하여 전처리 및 후처리 작업을 수행하는 방법이다.

2. 메이븐 종속성

인터셉터를 사용하려면 pom.xmlspring-web 종속성을 포함한다.

1
2
3
4
5
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.3.13</version>
</dependency>

3. 스프링 핸들러 인터셉터

Spring 인터셉터는 HandlerInterceptorAdapter 클래스를 확장하거나 HandlerInterceptor 인터페이스를 구현하는 클래스이다.

HandlerInterceptor에는 세 가지 주요 메서드가 포함되어 있다.

  • prehandle() - 실제 핸들러 실행 전에 호출

  • postHandle() - 핸들러가 실행된 후 호출

  • afterCompletion() - 완료 요청이 완료되고 뷰가 생성된 후 호출

이 세 가지 방법은 모든 종류의 사전 및 사후 처리를 수행할 수 있는 유연성을 제공한다.

다음은 간단한 preHandle() 구현이다.

1
2
3
4
5
6
7
8
@Override
public boolean preHandle(
  HttpServletRequest request,
  HttpServletResponse response, 
  Object handler) throws Exception {
    // your code
    return true;
}

이 메서드는 boolean 값을 반환한다. 요청을 추가로 처리(true)하거나 처리하지 않도록(false) Spring에 지시한다.

다음으로 postHandle() 구현이 있다.

1
2
3
4
5
6
7
8
@Override
public void postHandle(
  HttpServletRequest request, 
  HttpServletResponse response,
  Object handler, 
  ModelAndView modelAndView) throws Exception {
    // your code
}

인터셉터는 요청을 처리한 직후 뷰를 생성하기 전에 이 메서드를 호출한다.

예를 들어 이 방법을 사용하여 로그인한 사용자의 아바타를 모델에 추가할 수 있다.

구현해야 하는 마지막 메서드는 afterCompletion() 이다.

1
2
3
4
5
6
7
@Override
public void afterCompletion(
  HttpServletRequest request, 
  HttpServletResponse response,
  Object handler, Exception ex) {
    // your code
}

이 방법을 사용하면 요청 처리가 완료된 후 사용자 지정 논리를 실행할 수 있다.

또한 여러 사용자 지정 인터셉터를 등록할 수 있다. 이를 위해 DefaultAnnotationHandlerMapping을 사용할 수 있다.

4. 커스텀 로거 인터셉터

웹 애플리케이션 로그인에 중점을 둔다.

먼저 클래스는 HandlerInterceptor를 구현해야 한다.

1
2
3
public class LoggerInterceptor implements HandlerInterceptor {
    ...
}

또한 인터셉터에서 로깅을 활성화해야 한다.

1
private static Logger log = LoggerFactory.getLogger(LoggerInterceptor.class);

이를 통해 Log4J는 로그를 표시하고 현재 어떤 클래스가 지정된 출력에 정보를 로깅하고 있는지 표시할 수 있다.

다음으로 사용자 정의 인터셉터 구현이다.

1) preHandle() 메서드

이름에서 알 수 있듯이 인터셉터는 요청을 처리하기 전에 preHandle()을 호출한다.

기본적으로 이 메서드는 true를 반환하여 요청을 처리기 메서드에 추가로 보낸다. 그러나 false를 반환하여 실행을 중지하도록 Spring에 알릴 수 있다.

후크를 사용하여 요청의 출처와 같은 요청 매개변수에 대한 정보를 기록할 수 있다.

이 예에서는 간단한 Log4J 로거를 사용하여 이 정보를 기록한다.

1
2
3
4
5
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    log.info("[preHandle][" + request + "]" + "[" + request.getMethod() + "]" + request.getRequestURI() + getParameters(request));
    return true;
}

요청에 대한 몇 가지 기본 정보를 기록하고 있다.

여기에서 비밀번호를 발견할 경우 물론 비밀번호를 기록하지 않도록 해야 한다. 간단한 옵션은 암호 및 기타 민감한 유형의 데이터를 별표로 바꾸는 것이다.

이를 수행하는 방법에 대한 빠른 구현은 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private String getParameters(HttpServletRequest request) {
    StringBuffer posted = new StringBuffer();
    Enumeration<?> e = request.getParameterNames();
    if (e != null) {
        posted.append("?");
    }
    while (e.hasMoreElements()) {
        if (posted.length() > 1) {
            posted.append("&");
        }
        String curr = (String) e.nextElement();
        posted.append(curr + "=");
        if (curr.contains("password") 
          || curr.contains("pass")
          || curr.contains("pwd")) {
            posted.append("*****");
        } else {
            posted.append(request.getParameter(curr));
        }
    }
    String ip = request.getHeader("X-FORWARDED-FOR");
    String ipAddr = (ip == null) ? getRemoteAddr(request) : ip;
    if (ipAddr!=null && !ipAddr.equals("")) {
        posted.append("&_psip=" + ipAddr); 
    }
    return posted.toString();
}

마지막으로 HTTP 요청의 소스 IP 주소를 얻는 것이다.

다음은 간단한 구현이다.

1
2
3
4
5
6
7
8
private String getRemoteAddr(HttpServletRequest request) {
    String ipFromHeader = request.getHeader("X-FORWARDED-FOR");
    if (ipFromHeader != null && ipFromHeader.length() > 0) {
        log.debug("ip from proxy - X-FORWARDED-FOR : " + ipFromHeader);
        return ipFromHeader;
    }
    return request.getRemoteAddr();
}

2) postHandle() 메서드

인터셉터는 핸들러 실행 후 DispatcherServlet이 보기를 렌더링하기 전에 이 메소드를 호출한다.

이를 사용하여 ModelAndView에 속성을 추가할 수 있다. 또 다른 사용 사례는 요청 처리 시간을 계산하는 것이다.

아래는 DispatcherServlet이 보기를 렌더링하기 직전에 요청을 간단히 기록한다.

1
2
3
4
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    log.info("[postHandle][" + request + "]");
}

3) afterCompletion() 메서드

뷰가 렌더링된 후 이 메서드를 사용하여 요청 및 응답 데이터를 얻을 수 있다.

1
2
3
4
5
6
7
8
9
@Override
public void afterCompletion(
  HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 
  throws Exception {
    if (ex != null){
        ex.printStackTrace();
    }
    log.info("[afterCompletion][" + request + "][exception: " + ex + "]");
}

5. 구성

사용자 정의 인터셉터를 추가한다.

그렇게 하려면 addInterceptors() 메서드를 재정의해야 한다.

1
2
3
4
@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LoggerInterceptor());
}

XML Spring 구성 파일을 편집하여 동일한 구성을 얻을 수 있다.

1
2
3
<mvc:interceptors>
    <bean id="loggerInterceptor" class="com.baeldung.web.interceptor.LoggerInterceptor"/>
</mvc:interceptors>

이 구성이 활성화되면 인터셉터가 활성화되고 애플리케이션의 모든 요청이 제대로 기록된다.

여러 개의 Spring 인터셉터가 구성된 경우 preHandle() 메소드는 구성 순서로 실행되는 반면 postHandle()afterCompletion() 메소드는 역순으로 호출된다.

[출처 및 참고]

This post is licensed under CC BY 4.0 by the author.