로그인을 한 사용자에게만 제공하는 화면을 로그인 하지 않은 사용자가 URL을 직접 호출했을 때, 접근하지 못하도록 로그인 여부를 확인하는 로직을 매번 작성하는 것은 매우 번거로운 일이다.
여러 로직에서 공통으로 필요로 하는 관심사인 공통 관심사는 스프링 AOP로 해결이 가능하지만, 웹과 관련된 공통 관심사는 서블릿 필터 또는 스프링 인터셉터를 사용하는 것을 권장한다.
* HTTP 요청 -> WAS -> 필터 -> 서블릿(스프링 디스패쳐 서블릿) -> 컨트롤러
* HTTP 요청 -> WAS -> 필터 (적절하지 않은 요청일 경우, 서블릿 호출 X)
* HTTP 요청 -> WAS -> 필터1 -> 필터2 -> 필터3 -> 서블릿(스프링 디스패쳐 서블릿) -> 컨트롤러
필터는 체인으로 구성되어, 중간에 필터를 자유롭게 추가가 가능하다.
또한, 필터는 특정 URL 패턴에 적용할 수 있다. (모든 요청 필터 적용 - /*
)
public interface Filter {
public default void init(FilterConfig filterConfig) throws ServletException {}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException;
public default void destroy() {}
}
init() :
필터의 초기화 메서드로, 서블릿 컨테이너가 생성될 때 호출doFilter() :
요청이 있을 때마다 해당 메서드가 호출된다. 해당 메서드에 로직 구현destroy() :
필터 종료 메서드로, 서블릿 컨테이너가 종료될 때 호출필터 로직
public class TempFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpReq = (HttpServletRequest) req;
String reqURI = httpReq.getRequestURI();
try {
... 생략 ...
chain.doFilter(req, res);
} catch (Exception e) {
throw e;
} finally {
log.info("필터 종료", reqURL)
}
}
}
~ implements Filter {}
: 필터를 사용하기 위해 필터 인터페이스를 구현해야 한다.
doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
: HTTP 요청이 아닐 경우까지 고려해서 만든 인터페이스로, HTTP의 경우에는 위와 같이 다운캐스팅
chain.doFilter(req, res)
: 다음 필터가 있으면 다음 필터를 호출하고, 없다면 서블리 호출한다.인증과 별개로 항상 허용해야 하는 페이지는 접근 가능하도록 화이트 리스트 경로 설정 !
필터 설정
@Configuration
public class WebConfig {
@Bean
public FilterRegistrationBean TempFilter() {
FilterRegistrationBean<Filter> filterBean = new FilterRegistrationBean<>();
filterBean.setFilter(new TempFilter());
filterBean.setOrder(1);
filterBean.addUrlPatterns("/*");
return filterBean;
}
}
setFilter
: 구현한 로직필터를 등록
setOrder(1)
: 필터의 순서를 적용
addUrlPatterns("/*")
: 원하는 경로에 필터를 적용할 수 있다.
스프링 인터셉터 역시 서블릿 필터와 같이 공통 관심사를 해결할 수 있는 기술이다. 이는 스프링 MVC가 제공하는 기술로, 적용되는 순서와 범위 그리고 사용 방법이 다르다.
* HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러
* HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 인터셉터 (적절하지 않은 요청일 경우, 컨트롤러 호출 X)
* HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 인터셉터 1 -> 인터셉터 2 -> 컨트롤러
필터와 다르게 인터셉트는 서블릿이 호출된 후, 컨트롤러 호출 직전에 호출 된다.
필터와 마찬가지로 인터셉터를 추가로 적용할 수 있다.
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest req, HttpServletResponse res,
Object handler) throws Exception {}
default void postHandle(HttpServletRequest req, HttpServletResponse res,
Object handler, @Nullable ModelAndView view) throws Exception {}
default void afterCompletion(HttpServletRequest req, HttpServletResponse res,
Object handler, @Nullable Exception ex) throws Exception {}
}
서블릿 필터의 경우, doFilter()
하나만 제공하지만, 스프링 인터셉터는 preHandle(컨트롤러 호출 전)
, postHandle(컨트롤러 호출 후)
, afterCompletion(요청 완료 이후)
을 제공한다.
예외 발생시,
postHandle()
은 호출 X
예외와 무관한 공통 처리를 위하면afterCompletion
을 사용하도록 하자.
인터셉터 로직
public class TempInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res,
Object handler) throws Exception {
String requestURI = req.getRequestURI();
HttpSession session = req.getSession(false);
if (session == null || session.getAttribute("loginId") == null) {
res.sendRedirect("/login?redirectURL=" + requestURI);
return false;
}
return true;
}
~implements HandlerInterceptor
: 인터셉터를 적용하기 위해 인터페이스 구현
인터셉터 설정
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TempInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns("/css/**", "/*.ico", "/error");
}
}
excludePathPatterns
: 필터의 화이트 리스트의 기능과 유사하다.
지금까지 정리한 필터와 인터셉터의 개념/비교는 위와 같다.
필터는 Request / Response 객체 조작 가능하지만, 인터셉터는 불가능하다. 여기서 말하는 조작은 내부 상태를 변경한다는 것이 아닌 다른 객체로 변경할 수 있다는 의미이다.
필터의 경우, 다음 필터를 호출하기 위해 필터 체이닝을 하는데 이 때, 원하는 Request / Response을 넣어줄 수 있다. 인터셉터의 경우, true
혹은 false
로 반환되기에 원하는 Request/Response 객체를 넣어줄 수 없다.
스프링 인터셉터가 개발자 입장에서 보다 더 편리한 환경으로 서블릿이 호출되기 전에 처리해야할 로직이 아니라면 스프링 인터셉터를 활용해보자 !
📌 본 포스트는 스프링 MVC 2편 - 백엔드 웹 개발 핵심 기술 통해 학습한 내용을 요약 및 정리한 것입니다.