[Spring] Filter(필터) VS Interceptor(인터셉터) 정리

wooSim·2023년 7월 5일
0
post-thumbnail

개발을 하다보면 로그인, 세션 관리, 권한 체크, 로그처리, 인코딩 등 공통적으로 처리해야하는 작업이 있습니다. 이번 글에서는 클라이언트에서 서버로 요청이 들어왔을 때의 일련의 공통 로직을 처리할 수 있는 기법 중 FilterInterceptor에 대해 정리하고 간단하게 구현해보겠습니다.



1. Filter(필터)와 Interceptor

아래 그림을 보면 필터와 인터셉터는 디스패처 서블릿 앞 뒤에 위치하는 것을 볼 수 있습니다. 즉, 클라이언트 Request -> Filter -> Dispatcher Servlet -> Interceptor 순서로 진행되고 응답할때도 역순으로 진행됩니다.
Dispatcher Servlet(디스패처 서빌릇)을 한마디로 정의하면 HTTP 프로토콜로 들어오는 모든 요청을 받아 컨트롤러에 위임해주는 프론트 컨트롤러 입니다.

그리고 그림에서 색으로 표시되어 있듯이 필터는 스프링컨텍스트 외부에 존재하여 스프링과 무관한 자원에 대해 동작하지만 인터셉터는 Dispatcher Servlet 후 컨트롤러를 호출하기 전 후로 요청을 가로채 Controller에 관한 요청과 응답에 대해 처리합니다. 이를 정리하자면

□ Filter(필터)

  • 스프링 컨텍스트 외부에 있고 디스패처 서블릿에 요청이 전달되기 전 후의 모든 요청에 대한 부가작업 이 가능
  • 일반적으로 인코딩 변환, 인증 및 인가 등의 요청에 대한 처리로 사용

□ Interceptor(인터셉터)

  • spring에서 제공하는 기술로 컨트롤러로 전 후의 요청이나 응답을 참조하거나 사용할 수있다.
  • 로그인 체크, 권한체크, Controller로 넘겨주는 데이터 가공 등의 요청에 대한 처리로 사용


2. Filter(필터) 간단 구현

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();
    }
}
  • init: 톰캣이 시작될 때 필터를 최초 한 번 인스턴스 생성(생략 가능)
  • doFilter: 클라이언트 요청이 디스패처 서블릿으로 전달되기 전에 웹 컨테이너에 의해 실행되는 메소드
  • destroy: 필터 인스턴스가 제거될 때 실행되는 메서드(생략 가능)

doFilter 메서드의 FilterChain을 통해 대상으로 요청을 전달하게 되며 필터가 여러개일 경우 상단의 @Order 어노테이션을 통해 필터간 순서를 지정할 수 있습니다.


만약 필터에 url-pattern을 매핑하고자 한다면 @WebFilter 어노테이션을 통해 가능합니다.
@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번 작동하게 됩니다.



3. Interceptor(인터셉터) 간단 구현

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");
    }
}
  • preHandle : 컨트롤 메서드가 호출되기 전에 실행
  • postHandle : 컨트롤 메서드가 호출된 후, view 페이지 렌더링 되기 전에 실행
  • afterCompletion : view 페이지 렌더링된 후, 즉 모든 작업 완료 후 실행

각각의 실행 시점은 위와 같이 차이가 있으며 처리를 원하는 시점에 맞게 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 메서드를 통해 해당 인터셉터를 적용시킬 요청을 추가하거나 제외시킬 수 있습니다.





부족한 부분이 많을텐데 읽어주셔서 감사합니다! 혹시 틀린 내용이나 코드가 있을 경우 언제든지 댓글 남겨주시면 감사하겠습니다!

profile
daily study

0개의 댓글