[Spring] 필터란 ? + 요청 로그 필터, 인증 체크 필터 예시

728x90

 

필터

서블릿 필터

사용자 인가 없이 URL 을 직접 호출해서 페이지에 접속하는 것을 막아주는 역할을 한다.

서블릿이 제공하는 기술로, 인터셉터와 비슷하지만 인터셉터가 더 편리하고, 정교하다.

 

필터 흐름

HTTP 요청 > WAS > 필터 > 서블릿 > 컨트롤러

 

필터가 호출된 다음 디스패처 서블릿이 호출되기 때문에,

필터를 통해 요청을 검토하여 적절하지 않은 요청이면 디스패처 서블릿을 호출하지 않는다.

 

필터 체인

HTTP 요청 > WAS > 필터1 > 필터2 > 필터3 > 서블릿 > 컨트롤러

 

또한 필터는  체인으로 구성되어 중간에 필터를 추가할 수 있다.

 

필터 인터페이스

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()` : 필터 초기화 메서드, 서블릿 컨테이너 생성 시 호출

`doFilter()` : 요청이 올 때마다 해당 메서드 호출

`destroy()` : 필터 종료 메서드, 서블릿 컨테이너 종료 시 호출

 

요청 로그 필터

@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 {
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    String requestURI = httpRequest.getRequestURI();
    String uuid = UUID.randomUUID().toString();
    
    try {
      log.info("REQUEST [{}][{}]", uuid, requestURI);
      chain.doFilter(request, response);
    } catch (Exception e) {
      throw e;
    } finally {
    log.info("RESPONSE [{}][{}]", uuid, requestURI);
    }
  }
  
  @Override
  public void destroy() {
    log.info("log filter destroy");
  }
}

`doFilter(ServletRequest request, ServletResponse response, FilterChain chain)`

  : 여기서 ServletRequest request 는 HTTP 요청이 아닌 경우까지 고려한 인터페이스이기 때문에 다운케스팅 하여 사용

`UUID` : Http 요청을 구분하기 위해 요청당 임의의 uuid 생성

`chain.doFilter(request, response)` : 다음 필터 있으면 필터 호출, 없으면 서블릿 호출 (없으면 다음 단계 진행 안 됨)

 

> 참고 <

doFilter 에서 다운캐스팅이 가능한 이유는 ??

다운 캐스팅이 가능한 이유는, 파라미터로 들어온 request 는 ServletRequest 타입이지만, 실제로 서블릿 컨테이너에서 전달하는 객체는 그의 자식인 HttpServletRequest 의 구현체다. 따라서 원래 HttpServletRequest 타입이므로 다운캐스팅이 가능하다.

 

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

필터를 등록해야 사용할 수 있다.

이때, 스프링부트를 사용하면 `FilterRegistrationBean` 을 사용하여 등록하면 된다.

 

`setFilter(new LogFilter())` : 등록할 필터 지정

`setOrder()` : 필터의 순서 (낮을수록 먼저 동작)

`addUrlPatterns()` : 필터를 적용할 URL 패턴 지정 (여러 개 가능)

 

인증 체크 필터

@Slf4j
public class LoginCheckFilter implements Filter {

  private static final String[] whitelist = {"/", "/members/add", "/login", "/logout","/css/*"};

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, 
                       FilterChain chain) throws IOException, ServletException {
                       
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    String requestURI = httpRequest.getRequestURI();
    HttpServletResponse httpResponse = (HttpServletResponse) response;
    
    try {
      log.info("인증 체크 필터 시작 {}", requestURI);
      
      if (isLoginCheckPath(requestURI)) {
        log.info("인증 체크 로직 실행 {}", requestURI);
        HttpSession session = httpRequest.getSession(false);
        
        if (session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null) {
          log.info("미인증 사용자 요청 {}", requestURI);
          httpResponse.sendRedirect("/login?redirectURL=" + requestURI);
          return;
        }
      }
      
      chain.doFilter(request, response);
    } catch (Exception e) {
      throw e;
    } finally {
      log.info("인증 체크 필터 종료 {}", requestURI);
    }
  }

  private boolean isLoginCheckPath(String requestURI) {
    return !PatternMatchUtils.simpleMatch(whitelist, requestURI);
  }
}

`whitelist = {"/", "/members/add", "/login", "/logout", "/css/*"};`

  : 인증과 무관하게 항상 허용할 URL 경로. 나머지 경로에 인증 체크 로직 실행

`httpResponse.sendRedirect("/login?redirectURL=" + requestURI);`

  : 미인증 사용자를 로그인 화면으로 리다이렉트

  : 현재 요청 경로 requestURI 를 쿼리 파라미터로 함께 전달하여 로그인 성공 시 해당 경로로 이동할 수 있도록 함

`return;`

  : 이후 서블릿, 컨트롤러가 호출되지 않고 요청이 끝남

 

@Bean
public FilterRegistrationBean loginCheckFilter() {
  FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
  filterRegistrationBean.setFilter(new LoginCheckFilter());
  filterRegistrationBean.setOrder(2);
  filterRegistrationBean.addUrlPatterns("/*");
  return filterRegistrationBean;
}

`setFilter(new LoginCheckFilter())` : 등록할 필터 지정

`setOrder()` : 필터의 순서 (낮을수록 먼저 동작)

`addUrlPatterns()` : 필터를 적용할 URL 패턴 지정 (여러 개 가능)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

출처 | 스프링 MVC 2(김영한) - 인프런

728x90