스프링

[Spring] 필터, 인터셉터

gilssang97 2021. 9. 26. 14:24

필터, 인터셉터란?

우리는 공통 비지니스 로직이 있다고 했을 때, 이 로직이 필요한 모든 클래스에 진행한다면 무수한 중복이 생길 것이고

책임이 커져 좋지 않은 상황이 발생할 것이다.

우리는 이를 해결하기 위해 공통 비지니스 로직을 따로 빼서 관리하는 방식으로 진행하면 좋을 것이다.

이를 할 수 있는 방법을 말하자면 필터, 인터셉터, AOP가 존재한다.

AOP에 대해서는 다뤄본적이 있으니 필터, 인터셉터에 대해서 알아보고자 한다.

전체적인 구조를 그림으로 살펴보면 다음과 같다.

  • 필터는 서블릿이 관리하고 Dispatcher Servlet 앞단에서 공통된 로직을 처리한다.
  • 인터셉터는 스프링이 관리하고 필터가 진행된 후 Dispatcher Servlet 뒷단에서 공통된 로직을 처리한다.
  • AOP는 인터셉터와 마찬가지로 스프링이 관리하고 인터셉터가 진행된 뒤 공통된 로직을 처리한다.

필터

필터는 다음과 같은 메소드로 관리를 진행한다.

  • init() - 필터 인스턴스 초기화
  • doFIlter() - 전/후 처리
  • destroy() - 필터 인스턴스 소멸

실제로 필터를 구현해보자.

(1) 로그

@Slf4j
public class LogFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String uri = httpServletRequest.getRequestURI();
        String uuid = UUID.randomUUID().toString();

        try {
            log.info("REQUEST [{}, {}]", uuid, uri);
            chain.doFilter(request, response);
        } catch(Exception e) {
            throw e;
        } finally {
            log.info("RESPONSE [{}, {}]", uuid, uri);
        }
    }
}
  • doFilter에 URI를 받아오고 UUID를 생성하여 이를 로그로 찍어준다.

(2) 로그인

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

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest httpServletRequest = (HttpServletRequest) request;
    HttpServletResponse httpServletResponse = (HttpServletResponse) response;

    if (isRequireLogin(httpServletRequest.getRequestURI()) && isLogin(httpServletRequest.getSession())) {
        httpServletResponse.sendRedirect("/login?redirectURL="+((HttpServletRequest) request).getRequestURI());
        return;
    }
    chain.doFilter(request, response);
}
public Boolean isRequireLogin(String requestURI) {
    return !PatternMatchUtils.simpleMatch(whitelist, requestURI);
}
public Boolean isLogin(HttpSession session) {
    if (session == null || session.getAttribute(SessionConst.LOGIN_STUDENT) == null) {
        return true;
    }
    return false;
}
  • 방문 가능한 리스트를 만들어 이 경우에는 필터링을 하지 않도록 한다.
  • 로그인된 상태인지 확인하고 아니라면 이동하려고 했던 위치를 파라미터로 저장하고 로그인 페이지로 이동한다. (로그인 후 바로 이동할 수 있도록 한다.)

(3) 필터 등록

@Configuration
public class WebConfiguration {

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

    @Bean
    public FilterRegistrationBean loginFilter() {
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new LoginFilter());
        filterRegistrationBean.setOrder(2);
        filterRegistrationBean.addUrlPatterns("/*");
        return filterRegistrationBean;
    }
}
  • 필터를 등록해준다.

(4) 결과

  • 로그인이 필요한 페이지로 접근시 로그인 페이지로 이동하여 로그인을 해야 한다.
  • URL을 보면 이동하려는 페이지가 파라미터로 적용되어 추후에 이곳으로 이동할 수 있도록 한다.

  • 로그인 후 잘 이동된 모습을 확인할 수 있다.

인터셉터

인터셉터는 다음과 같은 메소드로 관리를 진행한다.

  • preHandler() - Controller 호출 전
  • postHandler() - Controller 호출 후 / view 렌더링 전
  • afterCompletion() - view 렌더링 후

(1) 로그

@Slf4j
public class LogInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String uri = request.getRequestURI();
        String uuid = UUID.randomUUID().toString();
        request.setAttribute("uuid", uuid);
        log.info("REQUEST [{}, {}, {}]", uuid, uri, handler);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("RESPONSE [{}, {}]", request.getAttribute("uuid"), request.getRequestURI());
    }
}
  • preHandle 부분에서 요청 로그를 기록하고 afterCompletion 부분에서 응답 로그를 기록한다.

(2) 로그인

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession(false);
        if (session == null || session.getAttribute(SessionConst.LOGIN_STUDENT) == null) {
            response.sendRedirect("/login?redirectURL="+request.getRequestURI());
            return false;
        }

        return true;
    }
}
  • 필터와 똑같은 방식으로 처리한다.
  • 리턴 값이 true라면 계속 진행하고 false라면 더이상 진행하지 않는다. (인터셉터는 물론 핸들러도 호출 X)

(3) 인터셉터 등록

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

        registry.addInterceptor(new LoginInterceptor())
                .order(2)
                .addPathPatterns("/**")
                .excludePathPatterns("/css/**", "/*.ico", "/login", "/logout", "/register", "/");
    }
}
  • WebMvcConfigurer를 상속받아 인터셉터를 추가한다.
  • Filter와는 다르게 excludePathPatterns를 통해 예외 페이지 처리가 가능하다.

(4) 결과

  • 로그인이 필요한 페이지로 접근시 로그인 페이지로 이동하여 로그인을 해야 한다.
  • URL을 보면 이동하려는 페이지가 파라미터로 적용되어 추후에 이곳으로 이동할 수 있도록 한다.

  • 로그인 후 잘 이동된 모습을 확인할 수 있다.

참고문헌

https://goddaehee.tistory.com/154

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=fortunerain&logNo=220964510870

https://www.inflearn.com/course/스프링-mvc-2/dashboard