[Spring MVC] 서블릿 필터, 스프링 인터셉터
- [ Backend ]/Spring Web
- 2022. 8. 14.
서블릿 필터
일반적으로 웹 서비스에서, 로그인을 해야 들어갈 수 있는 페이지들이 존재한다. 서블릿 필터가 없다면 이러한 기능을 위해서 각각의 컨트롤러에서 클라이언트가 알맞은 권한을 지녔는지 확인하고, 기능 추가시마다 별도로 필터를 추가해주어야 한다. 서블릿 필터는 클라이언트로부터 서버로 요청이 들어오면 서블릿(스프링이라면 Dispatcher Servlet으로 이해) 실행 직전에 필터링해준다.
권한O 사용자 : HTTP 요청 -> 서블릿 컨테이너 -> 필터 -> 서블릿 -> 컨트롤러
권한X 사용자 : HTTP 요청 -> 서블릿 컨테이너 -> 필터(적절하지 않은 요청이라 판단, 서블릿 호출X)
이러한 필터는 하나 이상으로 구성될 수도 있다.
필터 사용
필터를 사용하려면 Filter 인터페이스를 구현하고, 설정정보에 FilterRegistrationBean 필터를 등록해주면 된다.
@Slf4j
public class LogFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestURI = httpRequest.getRequestURI();
try {
chain.doFilter(request, response); //다음 필터가 있으면 호출
} catch (Exception e) {
throw e;
} finally {
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {...}
@Override
public void destroy() {...}
}
간단하게 요청에 맞추어 로그를 찍어주는 필터이다.
- doFilter : HTTP 요청이 오면 호출되는 메소드. HttpServletRequest, HttpServletResponse로 타입 캐스팅해서 사용하면 된다. 추가로, doFilter내에 chain.doFilter(request, response);를 호출하여 다음 필터가 있으면 호출하고, 없으면 서블릿을 호출하게 한다. chain.doFilter를 호출하지 않으면 다음 필터가 호출되지 않는것이 아니라 그냥 서블릿 자체가 호출이 되지 않는다.
- init, destroy : 필터 초기화/종료시 호출되는 메소드
@Configuration
public class WebConfig {
@Bean
public FilterRegistrationBean logFilter() { //스프링부트가 WAS를 띄울 때 필터를 같이 넣어줌
FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
filterFilterRegistrationBean.setFilter(new LogFilter()); //등록할 필터를 지정
filterFilterRegistrationBean.setOrder(1); //필터의 순서를 지정
filterFilterRegistrationBean.addUrlPatterns("/*"); //필터를 적용할 URL 패턴
return filterFilterRegistrationBean;
}
}
.setFilter() : 등록할 필터를 지정
.setOrder() : 등록할 필터의 순서를 지정
.setUrlPatterns("") : 등록할 필터를 적용할 URL 패턴.
스프링 인터셉터
서블릿 필터와 비슷하게 웹과 관련된 접근권한을 관리할 수 있는 기술이지만, 서블릿 필터와 다르게 서블릿이 아닌 스프링 MVC가 제공하는 기술이다.
권한O 사용자: HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 -> 컨트롤러
권한X 사용자: HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터(컨트롤러 호출 X)
스프링 인터셉터는 Dispatcher Servlet과 컨트롤러 사이에서 컨트롤러 호출 직전에 호출된다.
또한 서블릿 필터와 비슷하게 체인으로 구성되어, 여러 개의 인터셉터를 추가하여 구성할 수 있다. 이처럼 서블릿 필터와 비슷한 기능을 제공하지만, 훨씬 더 편리하고 정교한 기능들을 제공한다.
> 스프링 mvc에서 http요청이 들어오면 dispatcher servlet에 의해서 적절한 컨트롤러가 호출되는데, 인터셉터는 컨트롤러 호출 전에 preHandle, 호출 후에 postHandle이 동작하고 뷰가 렌더링된 후에도 afterCompletion이 호출되어 로직을 처리할 수 있다.
- 만약 preHandle의 응답값이 false면 나머지 인터셉터와 핸들러 어댑터(컨트롤러)도 호출되지 않는다.
- 컨트롤러에서 예외가 발생하면 postHandle은 호출되지 않는다.
- 단, afterCompletion은 예외가 발생해도 호출된다. 또한 예외를 파라미터로 받아서 어떤 예외가 발생했는지 찍어볼 수 있다.
인터셉터 사용
스프링 인터셉터를 사용하기 위해서는 HandlerInterceptor 인터페이스를 구현하여 사용하면 된다.
@Slf4j
public class LogInterceptor implements HandlerInterceptor {
public static final String LOG_ID = "logId";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
String uuid = UUID.randomUUID().toString();
request.setAttribute(LOG_ID, uuid); //afterCompletion으로 넘기기
if(handler instanceof HandlerMethod) { //핸들러 메소드 정보를 받아서 처리가능
HandlerMethod hm = (HandlerMethod) handler;
//@RequestMapping: HandlerMethod
//정적 리소스: ResourceHttpRequestHandler
}
log.info("REQUEST [{}][{}][{}]", uuid, requestURI, handler);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle [{}]", modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
String requestURI = request.getRequestURI();
String uuid = (String)request.getAttribute(LOG_ID);
log.info("RESPONSE [{}][{}][{}]", uuid, requestURI, handler);
if(ex!=null)
log.error("afterCompletion error!!", ex);
}
}
요청이 동작할 때 로그를 찍어주는 인터셉터이다. 핸들러의 인자를 보면 필터와 다르게 핸들러 동작 정보도 받을 수 있다. 필터와 다르게 호출 시점이 분리되어 있기 때문에 로그 구분을 위해서 찍는 uuid 전달을 위해서 preHandle 메서드에서 setAttribute로 로그 id를 전달하고, afterCompletion에서 getAttribute로 받는 것을 볼 수 있다.
그리고 인터셉터 사용을 위해서는 WebConfig에서 WebMvcConfigurer를 구현해서 addInterceptors함수를 오버라이딩 해주면 된다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns("/css/**", "/*.ico", "/error" );
}
- addInterceptor(): 동작할 인터셉터 설정
- order(): 순서 설정
- addPathPatterns("/**"): 인터셉터를 적용할 URL패턴 지정
- excludePathPatterns("/css/**", "/*.ico", "/error"): 인터셉터에서 제외할 패턴 설정. 인터셉터는 addPathPatterns, excludePathPatterns와 같은 메소드를 이용하여 보다 정밀하게 URL 패턴을 지정할 수 있다고, 그렇기 때문에 서블릿 필터와 다르게 필터에서 접근을 별도로 관리해줄 필요가 없다.
보다시피 필터가 먼저 호출되고, 인터셉터가 호출된다. 종료시에는 반대로 인터셉터 종료 후 필터가 종료된다.
(1번 필터 동작 > 2번 필터 동작 > 1번 인터셉터 preHandle > 컨트롤러 > 1번 인터셉터 postHandle > 1번 인터셉터 afterCompletion > 2번 필터 종료 > 1번 필터 종료)
참고) URL 패턴 설정 방법
'[ Backend ] > Spring Web' 카테고리의 다른 글
[Spring MVC] 스프링 API 에러처리 (0) | 2022.08.22 |
---|---|
[Spring MVC] 스프링 HTTP 에러 처리 (0) | 2022.08.18 |
[Spring MVC] 쿠키+세션을 사용한 로그인 (0) | 2022.08.09 |
[Spring MVC] 쿠키를 사용한 로그인 (0) | 2022.08.08 |
[Spring MVC] 검증 2 - Bean Validation (0) | 2022.07.14 |