Http 요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러
필터 인터페이스를 구현하고 등록하면 '서블릿 컨테이너'가 필터를 싱글톤 객체로 생성하고, 관리한다.
- init(): 필터 초기화 메서드, 서블릿 컨테이너가 생성될 때 호출된다.
- doFilter(): 고객의 요청이 올 때 마다 해당 메서드가 호출된다. 필터의 로직을 구현하면 된다.
- destroy(): 필터 종료 메서드, 서블릿 컨테이너가 종료될 때 호출된다
package hello.login.web.filter; import lombok.extern.slf4j.Slf4j; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.UUID; @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 { log.info("log filter doFilter"); // http요청이 오면 doFilter 호출출 //고객 요청 마다 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"); } } 모든 요청을 로그로 남기는 필터이다.
@Bean public FilterRegistrationBean logFilter() { FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>(); filterRegistrationBean.setFilter(new LogFilter()); //등록할 필터 지정 filterRegistrationBean.setOrder(1); //순서 정하기 filterRegistrationBean.addUrlPatterns("/*"); //필터를 적용할 URL 패턴 return filterRegistrationBean; } ```
package hello.login.web.filter; import hello.login.web.SessionConst; import lombok.extern.slf4j.Slf4j; import org.springframework.util.PatternMatchUtils; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; @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); //로그인으로 redirect httpResponse.sendRedirect("/login?redirectURL=" + requestURI); //상품 등록 누르고 로그인 화면으로 이동 뒤 로그인 후 다시 상품목록 화면으로 이동 하기 위해 URI넘겨주기 return; } } chain.doFilter(request, response); }catch (Exception e){ throw e; //예외 로깅 가능 하지만, 톰캣까지 예외를 봬주어야 함 }finally { log.info("인증 체크 필터종료{} ", requestURI); } } /** * 화이트 리스트의 경우 인증 체크X */ private boolean isLoginCheckPath(String requestURI) { return !PatternMatchUtils.simpleMatch(whitelist, requestURI); //화이트리스트에 없는 false } }
public FilterRegistrationBean loginCheckFilter() { FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>(); filterRegistrationBean.setFilter(new LoginCheckFilter()); filterRegistrationBean.setOrder(2); filterRegistrationBean.addUrlPatterns("/*"); return filterRegistrationBean; } }
인터셉트는 웹과 관련된 공통 관심 사항을 효과적으로 해결할 수 있는 기술이다.
스프링MVC가 제공하는 기술이다.
HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 -> 컨트롤러 //로그인 사용자
HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터(적절하지 않은 요청이라 판단, 컨트롤러 호출 (X) // 비 로그인 사용자
preHandle : 컨트롤러 호출 전에 호출된다.
postHandle : 컨트롤러에서 예외가 발생하면 postHandle 은 호출되지 않는다.
afterCompletion
afterCompletion 은 항상 호출된다.package hello.login.web.interceptor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.UUID; @Slf4j public class LogInterceptor implements HandlerInterceptor { public static final String LOG_ID = "logId"; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String requestURI = request.getRequestURI(); String uuid = UUID.randomUUID().toString(); //요청 로그 구분하기 위한 uuid생성 request.setAttribute(LOG_ID, uuid); //@RequestMapping: HandlerMethod //정적 리소스: ResourceHttpRequestHandler if (handler instanceof HandlerMethod) { HandlerMethod hm = (HandlerMethod) handler;//호출할 컨트롤러 메서드의 모든 정보가 포함되어 있다. } log.info("REQUEST [{}][{}][{}]", uuid, requestURI, handler); //return false; // 끝끝 return true; //다음 인터셉터나, 컨트롤러 호출 } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { log.info("postHandle [{}]", modelAndView); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //에외가 발생하면 postHanle은 호출이안되는데 애프터는 호출이됨 String requestURI = request.getRequestURI(); String logId = (String) request.getAttribute(LOG_ID); log.info("RESPONSE [{}][{}][{}]", logId, requestURI, handler); if (ex != null) { log.error("afterCompletion error!!", ex); } } }
package hello.login.web.interceptor; import hello.login.web.SessionConst; import lombok.extern.slf4j.Slf4j; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; @Slf4j public class LoginCheckInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String requestURI = request.getRequestURI(); log.info("인증 체크 인터셉터 실행{}", requestURI); HttpSession session = request.getSession(); if(session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null){ log.info("미인증 사용자 요청"); //로그인으로 redirect response.sendRedirect("/lgon?redirectURL=" + requestURI); } return true; } } 스프링 인터셉터는 코드가 매우 간결하다 인증이라는것은 컨트롤러 호출전에만 호출 되면 되기 때문에 preHandle만 구현하면 된다.
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LogInterceptor()) .order(1) .addPathPatterns("/**") .excludePathPatterns("/css/**", "/*.ico", "/error"); registry.addInterceptor(new LoginCheckInterceptor()) .order(2) .addPathPatterns("/**") //인터셉터 적용할 곳 .excludePathPatterns( "/", "/members/add", "/login", "/logout", "/css/**", "/*.ico", "/error" ); //인터셉터 적용 안할 곳 }
정리를 하자면
서블릿 필터'와 '스프링 인터셉터'는 웹과 관련된 공통 관심사를 해결하기 위한 기술이다.
스프링 인터셉터는 필터보다 비교적 편하다
특별한 문제가 없다면 인터셉터를 사용하는 것이 좋다.