티스토리 뷰

스프링 프레임워크에서 필터와 인터셉터는 웹 애플리케이션의 요청 및 응답을 처리하기 전후에 실행되는 컴포넌트이다.

 

스프링에서 컨트롤러로 요청이 들어오기 전에 처리해야 하는 작업이 있다. 애플리케이션 여러 로직에서 공통으로 관심이 있는 있는 것을 공통 관심사(cross-cutting concern)라고 하는데 대표적으로 인증, 인가, 로깅 등이 있고 주로 필터와 인터셉터를 통해 해당 기능을 수행한다.

 

필터와 인터셉터 두 컴포넌트 모두 비슷한 목적을 가지고 있지만, 사용되는 시점과 적용되는 방식에 차이가 있다.

 

필터(Filter)

public interface Filter {

    public default void init(FilterConfig filterConfig) throws ServletException {}

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException;

    public default void destroy() {}
}

필터는 Servlet 컨테이너 레벨에서 작동하며, HTTP 요청 및 응답에 대한 처리를 수행한다. 스프링에서 필터 인터페이스의 패키지를 보면

package javax.servlet;

javax.servlet 에 위치하고 있다. 즉, 스프링 프레임워크와는 무관하게 서블릿 컨테이너 레벨에서 작동한다는 것이다.

Filter 인터페이스 첫 줄의 설명을 읽어보면 (한국어로 번역했다..)

 

필터(Filter)는 서블릿(Servlet)이나 정적인 콘텐츠(static content)에 대한 요청이나 응답, 혹은 둘 다에 대한 필터링 작업을 수행하는 객체이다. 

 

즉, 필터는 서블릿이 지원하는 수문장이고, 필터를 적용하면 필터가 호출 된 다음에 서블릿이 호출된다. 그래서 만약 모든 고객의 요청 로그를 남기는 요구사항이 있다면 필터를 사용하면 된다.

 

필터는 체인으로 구성되는데, 중간에 필터를 자유롭게 추가할 수 있다. 위에서 작성한 Filter 인터페이스를 보면 doFilter 라는 메서드를 확인할 수 있다.

 

init: 필터 객체를 초기화하고 서비스에 추가하는 메서드

doFilter: 처리를 구현해서 동작한 후, 파라미터로 받은 FilterChain을 사용해서 다음 대상으로 요청을 넘겨줌

destroy: 필터를 제거하고 자원을 반환하기 위한 메서드

 

여기서 doFilter의 첫 번째 파라미터가 ServletRequest임을 확인할 수 있는데, 이는 Http 요청이 아닌 경우까지 고려해서 만들었다고 한다. HttpServletRequest를 사용하려면 다운캐스팅해서 사용하면 된다.

 

필터를 커스텀해서 사용하는 방법은 Filter 인터페이스를 적절히 구현한 후

public class CustomFilter implements Filter {
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("log: filter doFilter Method");
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

FilterRegistrationBean을 이용해 filter를 bean으로 등록하면 된다. 필터를 bean으로 등록할 때 순서와 url 패턴도 설정할 수 있다.

@Configuration
public class WebConfig {

    @Bean
    public FilterRegistrationBean registerFilter() {
        FilterRegistrationBean<Filter> filterRegistrationBean = new
                FilterRegistrationBean<>();
        
        filterRegistrationBean.setFilter(new CustomFilter());
        filterRegistrationBean.setOrder(1);
        filterRegistrationBean.addUrlPatterns("/*");
        return filterRegistrationBean;
    }
}

 

 


인터셉터(Interceptor)

public interface HandlerInterceptor {

    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        return true;
    }

    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            @Nullable ModelAndView modelAndView) throws Exception {
    }

    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
            @Nullable Exception ex) throws Exception {
    }
}

인터셉터는 스프링 프레임워크 레벨에서 작동하며, 주로 컨트롤러의 요청 처리 전후에 실행된다. 인터셉터도 필터와 같이 웹과 관련된 공통 관심 사항을 효과적으로 해결할 수 있는 기술이지만적용되는 순서와 범위그리고 사용방법이 다르다.

 

인터셉터는 HandlerInterceptor 인터페이스를 구현해서 사용할 수 있다. HandlerInterceptor의 패키지를 보면 

package org.springframework.web.servlet;

org.springframework.web.servlet에 위치하고 있다. 즉 스프링 컨텍스트 내부에서 작동한다는 것이다. DispatcherServlet은 내부적으로 핸들러 매핑을 통해 적절한 컨트롤러를 찾도록 요청하는데 그 결과로 HandlerExecutionChain을 반환한다.

 

실행 체인은 스프링 컨테이너에 인터셉터가 등록되어 있다면 인터셉터들을 순차적으로 거친 후에 컨트롤러를 실행하도록 한다.

 

HandlerInterceptor 인터페이스에는 preHandle, postHandle, afterCompletion 메서드가 존재한다.

 

preHandle:  반환 값으로 true와 false를 반환하는데, true인 경우 다음 인터셉터를 호출하고 false인 경우 요청을 종료

postHandle:  컨트롤러가 호출된 직후에 실행

afterCompletion: 요청이 완료된(뷰 응답) 이후에 실행

@Component
public class LoginInterceptor implements HandlerInterceptor {

    private static final String AUTHORIZATION = "Authorization";
    private static final String MEMBER_ERROR_MESSAGE = "일치하는 회원이 없습니다.";
    private static final String PASSWORD_ERROR_MESSAGE = "비밀번호가 일치하지 않습니다.";

    private final BasicAuthorizationExtractor extractor;
    private final MemberDao memberDao;

    public LoginInterceptor(BasicAuthorizationExtractor extractor, MemberDao memberDao) {
        this.extractor = extractor;
        this.memberDao = memberDao;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        MemberDto memberDto = extractor.extract(request.getHeader(AUTHORIZATION));
        validate(memberDto);

        return true;
    }

    private void validate(MemberDto memberDto) {
        MemberEntity findMember = memberDao.findByEmail(memberDto.getEmail())
                .orElseThrow(() -> new MemberNotFoundException(MEMBER_ERROR_MESSAGE));

        if (findMember.hasDifferPassword(memberDto.getPassword())) {
            throw new MemberNotFoundException(PASSWORD_ERROR_MESSAGE);
        }
    }
}

이번 장바구니 미션에 사용한 로그인 요청을 처리하는 인터셉터이다. preHandle 메서드에서 httpRequest 헤더의 Authorization 값을 확인한 후, 회원인지 아닌지 판단한다.

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {

    private final LoginInterceptor loginInterceptor;

    public WebMvcConfiguration(LoginInterceptor loginInterceptor) {
        this.loginInterceptor = loginInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/carts/**");
    }
}

인터셉터를 구현한 후 WebMvcConfigure의  addInterceptos 메서드를 오버라이드 해 registry에 등록하면 된다. 필터와 비교해보면 인터셉터는 addPathPatterns , excludePathPatterns 로 정밀하게 URL 패턴을 지정할 수 있다.

 

이렇게 되면 인터셉터를 이용해 컨트롤러에 요청이 도달하기 전 인증된 사용자를 판단할 수 있다.

 


차이점

 

그렇다면 필터와 인터셉터 둘 중 어떤 걸 사용해야 할까?

 

선택의 가장 큰 기준은 어떤 시점에 사용할 것인가가 중요한 것 같다.

 

필터는 서블릿 컨텍스트에 등록하고, 인터셉터는 스프링 컨텍스트에 등록한다.

 

필터는 서블릿에서 처리하는 과정의 전후를 다룰 수 있으므로 스프링과 무관하게 전역적으로 처리하는 작업들을 수행할 때 사용할 수 있을 것이다. 예를 들어 모든 요청의 로그를 남기거나, 인증 및 권한 부여, 캐싱을 할 수 있다.

 

인터셉터는 스프링 내부에서 컨트롤러가 처리하는 과정의 전후를 다룰 수 있으므로 컨트롤러에 넘겨주는 데이터를 가공하거나 처리할 때 사용할 수 있다. 예를 들어 토큰 유효성을 검사하거나, 인증 및 권한 부여, 캐싱을 할 수 있다.

 

이렇게 적고 보니 필터와 인터셉터는 거의 유사한 기능을 한다. 다만 어떤 시점에 사용할 것인가에서 큰 차이가 발생한다. 또한 필터의 경우 요청 파라미터를 ServletRequest로 받아 인터셉터보다 폭 넓은 요청을 처리할 수 있다는 장점이 있고, 인터셉터의 경우 필터보다 url을 좀 더 정교하게 다룰 수 있는 장점이 있다. 

댓글