개발을 하다보면 로그인, 세션 관리, 권한 체크, 로그처리, 인코딩 등 공통적으로 처리해야하는 작업이 있습니다. 이번 글에서는 클라이언트에서 서버로 요청이 들어왔을 때의 일련의 공통 로직을 처리할 수 있는 기법 중 Filter 와 Interceptor에 대해 정리하고 간단하게 구현해보겠습니다.
아래 그림을 보면 필터와 인터셉터는 디스패처 서블릿 앞 뒤에 위치하는 것을 볼 수 있습니다. 즉, 클라이언트 Request -> Filter -> Dispatcher Servlet -> Interceptor 순서로 진행되고 응답할때도 역순으로 진행됩니다.
Dispatcher Servlet(디스패처 서빌릇)을 한마디로 정의하면 HTTP 프로토콜로 들어오는 모든 요청을 받아 컨트롤러에 위임해주는 프론트 컨트롤러 입니다.
그리고 그림에서 색으로 표시되어 있듯이 필터는 스프링컨텍스트 외부에 존재하여 스프링과 무관한 자원에 대해 동작하지만 인터셉터는 Dispatcher Servlet 후 컨트롤러를 호출하기 전 후로 요청을 가로채 Controller에 관한 요청과 응답에 대해 처리합니다. 이를 정리하자면
Filter 클래스를 만들어 주고 상단에 @Component 어노테이션을 사용하여 @ComponentScan 대상으로 만들어 줍니다. jakarta.servlet.Filter 인터페이스를 implements 받으면 3가지 메소드를 override 할 수 있습니다.
@Slf4j
@Order(1)
@Component
public class CustomFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info(":::필터 인스턴스 초기화:::");
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
log.info(":::필터를 통한 응답 인코딩:::");
response.setCharacterEncoding("UTF-8");
chain.doFilter(request, response);
}
@Override
public void destroy() {
log.info(":::필터 인스턴스 종료:::");
Filter.super.destroy();
}
}
doFilter 메서드의 FilterChain을 통해 대상으로 요청을 전달하게 되며 필터가 여러개일 경우 상단의 @Order 어노테이션을 통해 필터간 순서를 지정할 수 있습니다.
@Slf4j
@WebFilter(value = "/login/*")
public class CustomFilter implements Filter {
....
}
이때 @WebFilter 어노테이션 내부에는 @Component 어노테이션이 없기 때문에 @ComponentScan의 대상이 아닙니다. @WebFilter을 스캔할 수 있도록 Application 클래스에 @ServletComponentScan 어노 테이션을 추가해줍니다.
@SpringBootApplication
@ServletComponentScan
public class SecurityStudyApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityStudyApplication.class, args);
}
}
이때 필터 클래스에 @Component 어노테이션을 지워주지 않으면 @ServletComponentScan + @WebFilter와 @ComponentScan + @Component 에 의해 중복 등록이 되므로 필터도 2번 작동하게 됩니다.
Interceptor 클래스에 로그를 찍어보도록 하겠습니다. Interceptor 클래스를 만들어 주고 org.springframework.web.servlet.HandlerInterceptor 인터페이스를 implements 받으면 3가지 메소드를 override 할 수 있습니다.
@Slf4j
public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String requestURI = request.getRequestURI();
log.info("[interceptor] requestURI : {}" , requestURI);
return true; // false -> 이후에 진행을 하지 않는다.
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
log.info("[interceptor] postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
log.info("[interceptor] afterCompletion");
}
}
각각의 실행 시점은 위와 같이 차이가 있으며 처리를 원하는 시점에 맞게 override 해서 사용하면 됩니다.
만들어진 인터셉터 클래스는 WebMvcConfigurer을 implements 받아 Web 관련 설정을 하는 클래스에서 add 시켜줍니다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(new LogInterceptor())//LogInterceptor 등록
.order(1)
.addPathPatterns("/**");
registry.addInterceptor(new LoginCheckInterceptor())
.order(2)
.addPathPatterns("/**")
.excludePathPatterns("/", "/user/**", "/login-proc");
}
}
위와같이 인터셉터가 여러개일 경우 order 메서드를 통해 순서를 지정해 줄 수 있으며, addPathPatterns 메서드와 excludePathPatterns 메서드를 통해 해당 인터셉터를 적용시킬 요청을 추가하거나 제외시킬 수 있습니다.
부족한 부분이 많을텐데 읽어주셔서 감사합니다! 혹시 틀린 내용이나 코드가 있을 경우 언제든지 댓글 남겨주시면 감사하겠습니다!