Spring - 필터 , 인터셉터

김승호·2024년 12월 17일
0

필터

필요성

특정 사용자만 해당하는 구역에 들어 갈 수 있는 권한을 주기 위해서는 필터 기능이 필요하다.
1) 로직에 redirect 기능을 넣어서 해결하면 된다고 생각하겠지만, 해당 기능을 추가해야 되는 곳이 1,2개가 아닐 경우 노가다성이 발생한다.
2) AOP를 사용하면 되지만, web과 관련된 공통사는 Spring – 인터셉터 or 서블릿 – 필터를 사용하는 것이 좋다.
3) 필터는 스프링 , 서블릿에서 제공하는 2가지의 종류가 있다. 필터지만 사용 방법 , 범위 , 요청 흐름이 다르다.

필터 (서블릿)

1. 요청별 흐름

  1. 기본 요청 흐름 : HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러
  2. 필터 제한 시
    1) HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러 //로그인 사용자
    2) HTTP 요청 -> WAS -> 필터(적절하지 않은 요청이라 판단, 서블릿 호출X) //비 로그인 사용자
  3. 필터 체인 : 요청에 대해 중간에 필터를 자유롭게 추가할 수 있다.
    HTTP 요청 -> WAS -> 필터1 -> 필터2 -> 필터3 -> 서블릿 -> 컨트롤러

2. 서블릿 필터 인터페이스

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

필터 인터페이스를 구현하고 등록하면 서블릿 컨테이너가 필터를 싱글톤 객체로 생성하고, 관리한다.

필터 객체 자체는 서블릿 컨테이너가 관리하고, 아래 @Bean설정은 필터 등록 설정 객체를 Spring 빈으로 등록한 것이다. (이 방식은 Spring이 서블릿 컨테이너와 상호작용하도록 도와준다.)

1) init(): 필터 초기화 메서드, 서블릿 컨테이너가 생성될 때 호출된다.
2) doFilter(): 고객의 요청이 올 때 마다 해당 메서드가 호출된다. 필터의 로직을 구현하면 된다.
3) destroy(): 필터 종료 메서드, 서블릿 컨테이너가 종료될 때 호출된다.

3. 구현 및 설정

1. 구현

해당 서블릿 필터 인터페이스를 클래스에 상속 받은 후 오버라이드 클래스를 구현하면 된다.

@Slf4j
 public class LogFilter implements Filter {
    @Override
 	public void init(FilterConfig filterConfig) throws ServletException 
    {
        log.info("log filter init");
    }
    @Override
 	public void doFilter(ServletRequest request, ServletResponse response, 
	FilterChain chain) throws IOException, ServletException 
	{
 		//요청 마다 수행할 코드
        chain.doFilter(request, response);
    }
    @Override
 	public void destroy() {
        log.info("log filter destroy");
    }
 }

1) chain.doFilter() : 다음 필터가 있으면 필터를 호출하고, 필터가 없으면 서블릿을 호출한다.

2. 설정

1) setFilter(new LogFilter()) : 등록할 필터를 지정한다.
2) setOrder(1) : 필터는 체인으로 동작한다. 따라서 순서가 필요하다. 낮을 수록 먼저 동작한다.
3) addUrlPatterns("/*") : 필터를 적용할 URL 패턴을 지정한다. 한번에 여러 패턴을 지정할 수 있다.
4) 설정 후 빈으로 등록한다.

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

4. 예외로 인한 재요청이 들어 올 경우

@Bean
 public FilterRegistrationBean logFilter() 
 {
 	FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
    filterRegistrationBean.setFilter(new LogFilter());
    filterRegistrationBean.setOrder(1);
    filterRegistrationBean.addUrlPatterns("/*");
    filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST);
 	return filterRegistrationBean;
}

1) filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST);을 통해 요청의 DispatcherType이 REQUEST(정상 요청)일 경우에만 실행 할 수 있도록 제약을 둘 수 있다.
2) 기본 값 : filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST)이다. (설정 안할 시 알아서 막아준다.)

인터셉터 (스프링)

1. 요청별 흐름

  1. 기본 요청 흐름 : HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 -> 컨트롤러
    dispatcher servlet에 들어오며 Controller가 호출 되기 직전에 호출되는 방식
  2. 인터셉터 제한
    1) HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 -> 컨트롤러 //로그인 사용자
    2) HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터(적절하지 않은 요청이라 판단, 컨트롤러 호출
    X) // 비 로그인 사용자
  3. 필터 체인
    HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 인터셉터1 -> 인터셉터2 -> 컨트롤러

2. 필터와의 차별성

  1. 서블릿 필터와 다르게 매우 정밀한 URL 설정이 가능하다.
    addPathPatterns , excludePathPatterns
  2. doFilter()만 쓰는 서블릿과 다르게 메서드의 기능들이 분리되어 있다.

3. 인터셉터 인터페이스

 public interface HandlerInterceptor {`
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {}
 	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를 구현해서 인터셉터를 사용하면 된다.

1. 인터페이스 시점 설명


1) preHandle : 컨트롤러 호출 전에 호출된다. (더 정확히는 핸들러 어댑터 호출 전에 호출된다.)
1-1) DispatcherServlet에 의해 컨트롤러로 매핑되기 전에 호출
2) postHandle : 컨트롤러 호출 후에 호출된다. (더 정확히는 핸들러 어댑터 호출 후에 호출된다.)
3) afterCompletion : 뷰가 렌더링 된 이후에 호출된다.

2. 예외 발생 시


1) preHandler 예외 발생 시 : postHandle - X , afterCompletion - O
2) postHandle 호출 시 : afterCompletion - O

3. afterCompletion은 무조건 호출 된다.

예외와 무관한 공통처리(finally처럼)를 하고 싶을 경우 여기에 작성하면 된다.

3. 구현 및 설정

1. 구현

public class LogInterceptor implements HandlerInterceptor {
    public static final String LOG_ID = "logId";
    //pre
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //요청별 로그를 분리하기 위해서 UUID SET
        String requestURI = request.getRequestURI();
        String uuid = UUID.randomUUID().toString();
        request.setAttribute(LOG_ID, uuid);
        //@RequestMapping: HandlerMethod
        //정적 리소스: ResourceHttpRequestHandler
        if (handler instanceof HandlerMethod) {
            HandlerMethod hm = (HandlerMethod) handler; //호출할 컨트롤러 메서드의 모든정보가 포함되어 있다.
        }
        log.info("REQUEST  [{}][{}][{}]", uuid, requestURI, handler);
        return true; //false 진행X
    }
    //Post
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle [{}]", modelAndView);
    }
    //after
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //요청별 로그를 분리하기 위해서 UUID GET
        String requestURI = request.getRequestURI();
        String logId = (String)request.getAttribute(LOG_ID);
        log.info("RESPONSE [{}][{}]", logId, requestURI);
        if (ex != null) {
            log.error("afterCompletion error!!", ex);
        }
    }
}

1) HandlerMethod : 핸들러 정보는 어떤 핸들러 매핑을 사용하는가에 따라 달라진다. 스프링을 사용하면 일반적으로 @Controller , @RequestMapping을 활용한 핸들러 매핑을 사용하는데, 이 경우 핸들러 정보로 HandlerMethod 가 넘어온다.

2. 설정

@Configuration
 public class WebConfig implements WebMvcConfigurer {
   @Override
   public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor())
                .order(1)
                .addPathPatterns("/**")
                .excludePathPatterns("/css/**", "/*.ico", "/error");
    }
 }

WebMvcConfigurer 구현해서 @override된 addInterceptors() 안에 매개변수를 이용해서 해당 필터를 등록한다. (싱글톤으로 등록된다)
1) registry.addInterceptor(new LogInterceptor()) : 인터셉터를 등록한다.
2) order(1): 인터셉터의 호출 순서를 지정한다. 낮을 수록 먼저 호출된다.
3) addPathPatterns("/") : 인터셉터를 적용할 URL 패턴을 지정한다.
4) excludePathPatterns("/css/
", "/*.ico", "/error") : 인터셉터에서 제외할 패턴을 지정한다

4. 예외로 인한 재요청이 들어 올 경우

	@Override
 	public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor())
                .order(1)
                .addPathPatterns("/**")
                .excludePathPatterns("/error", "/error-page/**" );//오류 페이지 경로
    }

1) excludePathPatterns을 통해 error페이지로 설정한 경로를 넣어주면 재요청 시 인터셉터 실행을 방지 할 수 있다.

서블릿 필터와 비교해서 스프링 인터셉터가 개발자 입장에서 훨씬 편리하며 기능이 많다. 특별한 문제가 없다면 인터셉터를 사용하는 것이 좋다.

참고

스프링MVC2편

profile
백준 : https://www.acmicpc.net/user/tmdghdhkdw (골드 - 2)

0개의 댓글