Post

Spring MVC HandlerInterceptor

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.