Spring

[Spring] Servlet Filter 사용하기

소범범 2023. 5. 29. 20:27

Title

Filter 는 애플리케이션의 여러 로직에서 공통 관심사 이슈를 처리하는 방법중 1가지 방법입니다.

스프링의 AOP로도 해결할 수 있지만 웹과 관련된 공통관심사는 서블릿 필터 또는 인터셉터를 사용하는 것이 좋습니다.

 

Text

왜일까요?

답은 애플리케이션이 Http요청에 응답하는 순서(Filter → Interceptor → AOP → Interceptor → Filter ) 에 있습니다.

 

Filter는 Http요청에서 가장 첫번째 요청이기 때문에스프링과 무관한 자원에 대해 동작하고 있습니다.

 

따라서,

Filter 는 스프링과 분리되어야 하는 작업이나 모든 요청에 대한 로깅 등에 사용되고,

Interceptor 는 모든 빈에 접근이 가능하기 때문에 controller에 넘겨주는 데이터의 가공등의 작업,

AOP는 주로 로깅, 트랜잭션, 에러 처리 등 비즈니스 단의 메서드 필터링을 위주로 사용하는게 관례입니다.

이 처럼 공통 관심사의 이슈는  요청 처리 서에 따라 역할을 분리하여 사용하는 것이 가장 적합하게 사용하는 방법입니다. 

 

 

 

먼저 필터의 흐름은 

Http 요청 -> was -> 필터 -> 서블릿 -> 컨틀롤러 입니다.

 

필터를 특정 URL 에 적용을 하고 적절하지 않은 요청은 종결시킬 수 있습니다.

많이 쓰는 예제는 로그인 여부 체크이며 김영한님의 스프링 강의에서도 서블릿 필터를 사용해 로그인 여부 체크를 진행했습니다.

 

 

먼저 필터를 사용하기 위해서는 필터 interface를 implements 해야합니다.

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(): 필터 종료 메서드, 서블릿 컨테이너가 종료될 때 호출

 

 

사용하고자 하는 필터 클래스를 생성한 후 doFilter를 구현하면 됩니다.

아래는 간단한 로그인 여부를 체크하는 필터입니다.

 

 

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

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("LoginCheckFilter doFilter");
        HttpServletRequest httpRequest = (HttpServletRequest) request; //down casting
        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);
                    //로그인페이지로 redirect
                    httpResponse.sendRedirect("/login?redirectURL=" + requestURI);
                    return;
                }
            }
            chain.doFilter(request, response);
        } catch (Exception e) {
            throw e; //예외 로깅 가능하지만, 톰캣까지 예외를 보내주어야 함.
        } finally{
            log.info("인증 체크 필터 종료 {}", requestURI);
        }
    }

 

가장 중요한 부분은 chain.doFilter(request, response) 입니다.

해당 코드는 다음 필터가 있으면 필터를 호출하고, 필터가 없으면 서블릿을 호출합니다.

만약 이 로직을 호출하지 않으면 다음 단계로 진행되지 않습니다.

 

 

다음은 config 를 설정입니다.

config를 설정해줘야 Filter 가 적용됩니다 .

package hello.login;

import hello.login.web.filter.LogFilter;
import hello.login.web.filter.LoginCheckFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;

@Configuration
public class WebConfig {

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

        return filterRegistrationBean;
    }

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

        return filterRegistrationBean;
    }
}

 

WebConfig라는 설정 클래스를 @Configuration 을 통해 스프링에 등록해주었고,

로그 필터와 로그인체크필터 2개의 필터를 해당 애플리케이션에 등록했습니다.

setFilter() 를 통해 인자로 필터 객체를 넘겨주고,

setOrder() 를 통해 필터가 실행될 순서를 설정하고,

addUrlPatterns() 를 통해 필터가 적용될 url을 설정했습니다.

 

 

Conclusion

여기까지 Servlet Filter를 적용하는 방법을 공부해 봤습니다!

다음은 Interceptor 는 어떻게 적용될 수 있고 Filter 와 어떤 차이점을 가지고 있는지 알아보겠습니다!

 

 

Reference

1. inflearn - 김영한의 스프링MVC 2편 벡엔드 웹 개발 활용 기술

2. https://goddaehee.tistory.com/154

3. https://goddaehee.tistory.com/154