Spring Boot Filter & Interceptor

과녁스·2021년 7월 23일
0

Spring

목록 보기
4/14
post-thumbnail
post-custom-banner

개요


스프링 Filter와 Interceptor에 대한 학습한 내용을 정리해보았습니다.

Filter(필터)


Filter(필터) 는 DispatcherServlet에 요청이 전달되기 전/후에 url 패턴에 맞는 모든 요청에 대해 부가작업을 처리할 수 있는 기능을 제공합니다.

DispatcherServlet은 스프링의 가장 앞 단에 존재하는 프론트 컨트롤러이므로, Filter는 스프링 범위 밖에서 처리가 됩니다.
스프링 컨테이너가 아닌 톰캣과 같은 웹 컨터에너에 의해 관리가 됩니다.

Spring Boot로 웹 서비스 제공 시 Encoding, XSS, CORS 이슈 그리고 HTTP 헤더 정보 등을 일괄적으로 설정하할 수 있는 기능입니다.

클라이언트로부터 최초/최종단계의 위치에 있으며, 사용자 요청/응답의 헤더 정보 등을 검사 및 설정/변경을 할 수가 있다. 또한 Spring에 의해 변경되기 전의 사용자의 순수한 요청/응답 값을 확인할 수 있다.

유일하게 ServletRequest, ServletResponse 객체를 변경할 수 있는 단계이다.

주로 request와 response의 로깅 용도로 활용한다.

주요 역할을 정리해보면 아래와 같습니다.
1. 요청 전처리: 클라이언트에서 들어오는 HTTP 요청을 컨트롤러가 처리하기 전에 필터를 통해 요청을 가로채어 필요한 처리를 수행할 수 있습니다.
2. 응답 후처리: 컨트롤러가 처리한 응답을 클라이언트로 보내기 전에 필터에서 응답에 대해 추가 작업을 할 수 있습니다.
3. 요청 및 응답 변환: 필터를 통해 요청 및 응답 내용을 수정할 수 있습니다.
4. 보안 및 인증: 필터는 보안 검사, 인증, 권한 부여 작업을 수행하는 데 많이 사용됩니다. 예를 들어, 인증 토큰을 검증하거나, 특정 URL에 접근 권한이 있는지 확인하는 작업이 필터에서 이루어질 수 있습니다.

구조

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() {}
}

init

init 메소드는 필터 객체를 초기화하고 서비스에 추가하기 위한 메소드입니다. 웹 컨테이너가 1회 init 메소드를 호출하여 필터 객체를 초기화하면 이후의 요청들은 doFilter를 통해 처리됩니다.

doFilter

doFilter 메소드는 url-pattern에 맞는 모든 HTTP 요청이 디스패처 서블릿으로 전달되기 전에 웹 컨테이너에 의해 실행되는 메소드입니다. doFilter의 파라미터로는 FilterChain이 있는데, FilterChain의 doFilter 통해 다음 대상으로 요청을 전달하게 됩니다. chain.doFilter() 전/후에 우리가 필요한 처리 과정을 넣어줌으로써 원하는 처리를 진행할 수 있습니다.

destroy

destroy 메소드는 필터 객체를 서비스에서 제거하고 사용하는 자원을 반환하기 위한 메소드입니다. 이는 웹 컨테이너에 의해 1번 호출되며 이후에는 이제 doFilter에 의해 처리되지 않습니다.

사용법

임베디드 WAS의 경우에는 자동 설정에 의해서 Filter를 구현할 클래스에 @Component 만 붙여줘도 필터가 등록된다.

@Component
@Slf4j
public class LogFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        ContentCachingRequestWrapper httpServletRequest = new ContentCachingRequestWrapper((HttpServletRequest) request);
        ContentCachingResponseWrapper httpServletResponse = new ContentCachingResponseWrapper((HttpServletResponse) response);

        chain.doFilter(httpServletRequest, httpServletResponse);
        // ...

    }
}

필터를 2개 이상 드록해야 한다면 순서를 주기 위해서 @Order 설정이 필요합니다.

@Component
@Order(1)
public class FirstFilter implements Filter {

	@Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
    	//...
    }
}

@Component로 필터를 등록하면 url 패턴을 지정할 수 없기 때문에 기본적으로 모든 url 패턴에 매핑이 됩니다.

@WebFilter 어노테이션을 이용하여 특정 url 패턴에 매핑되는 필터를 추가할 수 있습니다.

@WebFilter(urlPatterns="/user/*")
public class SecondFilter implements Filter {

	@Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
    	//...
    }
}

ServletComponentScan

Spring Boot 내장 웹서버를 사용할 경우 서블릿 Component(@WebFilter, @WebServlet, @WebListener)를 스캔할 때 사용하는 어노테이션입니다.

@SpringBootApplication
@ServletComponentScan
public class ExampleApplication {

	public static void main(String[] args) {
    	SpringApplication.run(ExampleApplication.clss, args);
    }
}

⚠️ @WebFilter로 등록하면 @Order가 작동하지 않습니다.

FilterRegistrationBean

Filter를 더욱 세밀한 제어가 필요한 경우 FilterRegistrationBean 을 통해 등록할 수 있습니다.

먼저 Filter에 어노테이션을 적용하지 않고 생성합니다.

public class FirstFilter implements Filter {

	@Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
    	//...
    }
}

이 후 FilterRegistrationBean을 사용하여 등록합니다.

@Configuration
public class ServletConfig {

	@Bean
    public FilterRegistrationBean<FirstFilter> firstFilter() {
    FilterRegistrationBean<FirstFilter> registrationBean = new FilterRegistrationBean<>();
    
    registrationBean.setFilter(new FirstFilter());
    registrationBean.addUrlPatterns("/user/*");
    registrationBean.setOrder(1);
    registrationBean.setName("first-filter");
    
    return registartionBean;
    }
    
    @Bean
    public FilterRegistrationBean<SecondFilter> secondFilter() {
    FilterRegistrationBean<SecondFilter> registrationBean = new FilterRegistrationBean<>();
    
    registrationBean.setFilter(new SecondFilter());
    registrationBean.addUrlPatterns("/second/*");
    registrationBean.setOrder(2);
    registrationBean.setName("second-filter");
    
    return registartionBean;
    }
}

FilterRegistrationBean을 사용하면 순서 설정과 url 패턴 설정이 모두 가능합니다.

Intercptor(인터셉터)


인터셉터(Interceptor)는 스프링의 Spring Context(ApplicationContext) 기능으로 임의의 URI를 호출 시 DispatcherServlet에서 해당 Controller가 처리되기 전과 후에 발생하는 이벤트입니다.

Filter와 유사한 기능이지만, Filter와의 차이점은 Spring Context에 등록 된다.
AOP와 유사하게 사용할 수 있으며, 주로 인증/권한 단계 또는 로깅 시 사용한다.

사용법

HandlerInterceptorAdapte 를 상속받아 구현하며 preHandle, postHandle, afterCompletion, afterConcurrentHandlingStarted 네개의 메서드를 포함하고 있다.

  • preHandle : 컨트롤러 실행 전 수행한다. 반환 값이 true일 경우 컨트롤러로 진입하고 false일 경우 진입하지 않는다. Object handler는 진입하려는 컨트롤러의 클래스 객체가 담겨있다.
  • postHandle : 컨트롤러 실행 후 View가 랜더링 되기 전에 수행한다.
  • afterCompletion : 컨트롤러 실행되고 view가 랜더링 된 후에 실행된다.
  • afterConcurrentHandlingStarted : 비동기 요청 시 PostHandle과 afterCompletion이 수행되지 않고 afterConcurrentHandlingStarted가 수행된다.

Interceptor 생성

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class HttpInterceptor implements HandlerInterceptor {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        logger.info("[preHandle]");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        logger.info("[postHandle]");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object object, Exception ex) throws Exception {
        logger.info("[afterCompletion]");
    }
}

Interceptor 등록

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new HttpInterceptor())
                .addPathPatterns("/*")
                .excludePathPatterns("/board"); // 해당 경로는 인터셉터가 가로채지 않는다.
    }
}

Filter와 Interceptor의 비교


이미지 출처 : https://justforchangesake.files.wordpress.com/2014/05/spring-request-lifecycle.jpg

위 그럼으로 먼저 설명하면 filter는 DispatcherServlet 앞에서 먼저 동작하고, interceptor는 DispatcherServlet에서 Controller(Handler)사이에서 작동합니다.

Filter와 Interceptor 차이

Filter와 Interceptor 차이

일부에서 잘못된 정보로 필터가 스프링 빈으로 등록되지 못하며, 빈을 주입 받을 수 없다고 하지만 이는 잘못된 설명입니다.
이는 매우 옛날의 이야기이며, 필터는 스프링 빈으로 등록이 가능하며, 다른 곳에 주입되거나 다른 빈을 주입받을 수도 있습니다.

Filter

  • 공통된 보안 및 인증/인가 관련 작업
  • 모든 요청에 대한 로깅 또는 감사
  • 이미지/데이터 압축 및 문자열 인코딩
  • Spring과 분리되어야 하는 기능 등

필터에서는 기본적으로 스프링과 무관하게 전역적으로 처리해야 하는 작업들을 처리할 수 있다.

대표적인 예시로 보안과 관련된 공통 작업이 있습니다. 필터는 인터셉터보다 앞 단에서 동작하기 때문에 전역적으로 해야하는 보안 검사(XSS 방어 등)를 하여 올바른 요청이 아닐 경우 차단을 할 수 있습니다. 그러면 스프링 컨테이너까지 요청이 전달되지 못하고 차단되므로 안정성을 더욱 높일 수 있습니다.

또한 필터는 이미지나 데이터의 압축이나 문자열 인코딩과 같이 웹 애플리케이션에 전반적으로 사용되는 기능을 구현하기에 적당합니다. Filter는 다음 체인으로 넘기는 ServletRequest/ServletResponse 객체를 조작할 수 있다는 점에서 Interceptor보다 훨씬 강력한 기술입니다.

Interceptor

  • 세부적인 보안 및 인증/인가 공통작업
  • API 호출에 대한 로깅 또는 감사
  • Controller로 넘겨주는 정보(데이터) 가공

인터셉터에서는 API 요청과 관련되어 전역적으로 처리해야 하는 작업들을 처리할 수 있다.

대표적으로 세부적으로 적용해야 하는 인증이나 인가와 같이 클라이언트 요청과 관련된 작업 등이 있다. 예를 들어 특정 그룹의 사용자는 어떤 기능을 사용하지 못하는 경우가 있는데, 이러한 작업들은 컨트롤러로 넘어가기 전에 검사해야 하므로 인터셉터가 처리하기에 적합하다.

또한 인터셉터는 필터와 다르게 HttpServletRequest나 HttpServletResponse 등과 같은 객체를 제공 받으므로 객체 자체를 조작할 수는 없다. 대신 해당 객체가 내부적으로 갖는 값은 조작할 수 있으므로 컨트롤러로 넘겨주기 위한 정보를 가공하기에 용이하다. 예를 들어 JWT 토큰 정보를 파싱해서 컨트롤러에게 사용자의 정보를 제공하도록 가공할 수 있는 것이다.

그 외에도 우리는 다양한 목적으로 API 호출에 대한 정보들을 기록해야 할 수 있다. 이러한 경우에 HttpServletRequest나 HttpServletResponse를 제공해주는 인터셉터는 클라이언트의 IP나 요청 정보들을 포함해 기록하기에 용이하다.

출처 및 참고🙏

profile
ㅎㅅㅎ
post-custom-banner

0개의 댓글