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
'Spring' 카테고리의 다른 글
| [Spring] SimpleDateFormat 사용 시 NumberFormatException 발생 원인 (1) | 2023.11.17 |
|---|---|
| [Spring] 클래스명 동일할 경우 swagger 에서 구분 못하는 이슈 해결 (0) | 2023.09.15 |
| [Spring] AOP (Aspect-Oriented Programming) 란? AOP로 로깅하기 (0) | 2023.04.12 |
| [Spring] @Valid 를 이용한 @RequestBody 객체 검증하기 (0) | 2023.03.27 |
| [Spring] @Builder + @NoArgsConstructor 사용 시 발생하는 이슈 (0) | 2023.03.27 |