[TIL] Filter - return과 doFilter()에 대해서

phdljr·2024년 4월 7일
0

TIL

목록 보기
69/70
post-custom-banner

JWT Refresh Token에 대해 공부를 하던 도중, 어느 부분에서 doFilter를 호출하는 것이 아닌, return을 호출하게 됐다.

필터는 무조건 FilterChain#doFilter()를 호출하라고 배웠던 것 같은데, return을 호출하면 어떻게 되는지 감이 잡히질 않았다.

Filter에 대해서 잠깐 알아보고, returndoFilter()의 차이점에 대해 알아보겠다.


Filter

  • 요청이 서블릿으로 전송되기 전에 요청을 가로채고 처리하는 데 사용할 수 있고, 서블릿 코드가 완료된 후 컨테이너가 클라이언트에 응답을 다시 보내기 전에 응답하는 데 사용할 수 있는 플러그형 Java 구성 요소
  • 공통 관심사인 서블릿에 요청을 보내기 전이나 후에, 공통적인 로직을 실행시키는데 많이 사용된다.
  • 스프링 프레임워크에선, 서블릿을 DispatcherServlet이라고 볼 수 있다.

Filter를 만드는 방법

Filter 인터페이스를 구현

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

    void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;

    default void destroy() {
    }
}

스프링에서 제공해주는 추상 클래스를 오버라이드

GenericFilterBean

  • GenericFilterBean은 스프링의 설정 정보를 포함하고 있는 필터이다.

    • doFilter 메소드를 오버라이드해야된다.

      public class TestFilter extends GenericFilterBean {
      
          @Override
          public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
              FilterChain filterChain) throws IOException, ServletException {}
      }

OncePerRequestFilter

  • OncePerRequestFilterGenericFilterBean를 상속받은 클래스이며, 요청당 한 번만 실행되는 특성을 가지고 있다.
    • 한 번의 요청에 의도치않게 필터가 여러 번 실행되는 것을 방지해준다.

      • ex) redirect로 인한 필터 체인 재호출
    • doFilterInternal 메소드를 오버라이드해야된다.

      public class TestFilter extends OncePerRequestFilter {
      
          @Override
          protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
              FilterChain filterChain) throws ServletException, IOException {}
      }

Spring Framework에서 Filter를 등록시키는 방법

FilterRegistrationBean를 통해 등록

  • 모든 필터의 기본 url은 /* 이다.
@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean<Filter> logFilter() {
        FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();

        bean.setFilter(new LogFilter());
        bean.setOrder(1);
        bean.addUrlPatterns("/*");

        return bean;
    }

    @Bean
    public FilterRegistrationBean<Filter> loginCheckFilter() {
        FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();

        bean.setFilter(new LoginCheckFilter());
        bean.setOrder(2);
        bean.setUrlPatterns(List.of("/1", "/2"));

        return bean;
    }
}

@Component로 등록

  • @Order로 순서를 지정해줄 수 있다.
  • URL을 설정시킬 수 없다.
@Slf4j
@Component
@Order(1)
public class LogFilter implements Filter {
    @Override
    public void init(
            FilterConfig filterConfig
    ) throws ServletException {
        log.info("LogFilter init()");
    }

    @Override
    public void destroy() {
        log.info("LogFilter destroy()");
    }

    @Override
    public void doFilter(
            ServletRequest request,
            ServletResponse response,
            FilterChain chain
    ) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        String requestURI = req.getRequestURI();
        log.info("[{}] LogFilter doFilter Start", requestURI);

        try {
            chain.doFilter(request, response);
        } finally {
            log.info("[{}] LogFilter doFilter End", requestURI);
        }
    }
}

@WebFilter + @Component로 등록

  • @WebFilter를 통해 적용될 URL을 설정해줄 수 있다.
  • @WebFilter + @ServletComponentScan 방식으로도 사용할 수 있지만, 이는 필터간에 순서를 보장하지 않는다.
@Slf4j
@WebFilter({"/login", "/items"})
@Component
@Order(1)
public class LogFilter implements Filter {
    @Override
    public void init(
            FilterConfig filterConfig
    ) throws ServletException {
        log.info("LogFilter init()");
    }

    @Override
    public void destroy() {
        log.info("LogFilter destroy()");
    }

    @Override
    public void doFilter(
            ServletRequest request,
            ServletResponse response,
            FilterChain chain
    ) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        String requestURI = req.getRequestURI();
        log.info("[{}] LogFilter doFilter Start", requestURI);

        try {
            chain.doFilter(request, response);
        } finally {
            log.info("[{}] LogFilter doFilter End", requestURI);
        }
    }
}

return과 doFilter()의 차이

  • doFilter()의 호출 횟수가 FilterChain에 등록된 Filter의 개수와 같아지는 시점에 서블릿을 호출하게 된다.

    • doFilter()의 종료 시점에 서블릿을 호출하는 것이 아니다.
    • 필터의 개수보다 적게 doFilter를 호출한 뒤, return을 호출하면 서블릿을 호출하지 못하게 되며 그 상태로 필터들이 하나씩 return되기 시작하면서 default status code200과 텅 빈 bodyresponseclient에게 내려간다.
  • 스프링과 스프링 시큐리티에서의 FilterChain 구현체인 VirtualFilterChain을 보면, 다음과 같이 currentPositionsize(등록된 필터의 개수)가 같을 때 originalChaindoFilter()를 호출한다.

    • originalChain은 개발자가 등록한 필터가 들어있지 않은, 기본적으로 등록된 필터 체인을 의미하는 것 같다.

  • 다음 필터가 존재하지 않을 시, PassThroughFilterChain에서 서블릿을 호출하는 모습을 볼 수 있다.

예시

@Component // default order = 2147483647 // 가장 뒤에 위치한 필터
@Slf4j
public class TestFilter1 implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        log.info("TestFilter1 호출됨");
        //로직이 끝났으니 return 되어 TestFilter1을 호출한 TestFilter2의 chain.doFilter 가 종료된다.
    }
}

@Component
@Slf4j
@Order(value = 2147483646) 
public class TestFilter2 implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        log.info("TestFilter2 호출됨");
        chain.doFilter(request, response); // 실행순서 2번
        log.info("TestFilter2 호출됨(after DoFilter[1])"); // API 호출 전 실행
        chain.doFilter(request, response); // 실행순서 3번 -> API 호출
        // 등록한 필터가 3개이므로, 3번의 doFilter가 호출되면 서블릿을 호출하게 된다.
        // 만약, doFilter를 여기서 한 번 더 호출하게 된다면, 서블릿을 다시 재호출하게 된다.
        log.info("TestFilter2 호출됨(after DoFilter[2])"); // API 호출 후 실행
        //모든 로직 실행 후 return -> TestFilter3의 chain.doFilter()로 진행
    }
}

@Component
@Slf4j
@Order(value = 2147483645) 
public class TestFilter3 implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        log.info("TestFilter3 호출됨");
        chain.doFilter(request, response); // 실행순서 1번
        log.info("TestFilter3 호출됨(after DoFilter)"); // API 호출 후 실행
    }
}

실행 결과


출처

https://www.inflearn.com/chats/786858/filter%EB%A5%BC-%EB%93%B1%EB%A1%9D%ED%95%98%EB%8A%94-4%EA%B0%80%EC%A7%80-%EB%B0%A9%EB%B2%95
https://developer-ping9.tistory.com/237
https://velog.io/@jmjmjmz732002/Springboot-OncePerRequestFilter-vs-GenericFilterBean

profile
난 Java도 좋고, 다른 것들도 좋아
post-custom-banner

0개의 댓글