-
SpringMVC 2 - 서블릿 필터와 인터셉터Web/Spring 2023. 11. 15. 23:42
- 서블릿을 호출하기 전에 특정한 처리를 해야할 때 (공통처리) 필터를 사용함
- 여러 로직에서 공통으로 관심 있는 것을 공통 관심사라고 함 (ex 인증)
- 공통 관심사는 AOP를 사용할 수도 있지만, 웹과 관련된 관심사는 필터나 인터셉터를 사용하는 것이 좋다.
(웹과 관련된 부가 기능을 제공해줌!)
* 필터나 인터셉터 구현체를 스프링 빈으로 등록한 뒤에 등록해도됨
만약 필터나 인터셉터 구현체를 만들기 위해 여러 의존객체가 필요하다면 빈으로 등록한 뒤 등록하도록 하자!
1. 서블릿 필터
> HTTP 요청 > WAS > 필터 > 서블릿 > 컨트롤러 순으로 작동함
> 필터에 특정 URL 패턴을 적용할 수 있다.
> 필터는 우선순위를 설정해서 여러개 둘 수 있다. (로그필터 > 로그인 필터)
> 필터 인터페이스는 아래와 같음
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() }
> 필터 또한 싱글톤으로 생성 관리됨
1.1 요청 로그 필터
public class LogFilter implements Filter{ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException{ HttpServletRequest req = (HttpServletRequest) request; String requestURI = req.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); } } }
- uuid 하나의 트랜잭션에 부여한 아이디
- 필터는 HTTP요청이 아닌 경우 까지 고려한 인터페이스 (HTTP 요청에선 request 다운캐스팅해서 사용하자)
- chain.doFilter를 통해 다음 Filter 호출 가능
* 디스팻쳐에서 발생한 에러는 필터를 통과해서 WAS까지 전달됨 중간에 오류처리하면, WAS까지 오류가 못가서 추후에 오류를 보기 위해 지금은 오류가 나면 던지게 함
1.2 필터 설정
@Configuration public class WebConfig{ @Bean public FilterRegisterBean logFilter(){ FilterRegisterBean<Filter> fb = new FilterRegisterBean<>(); fb.setFilter(new LogFilter()); fb.setOrder(1); fb.addUrlPatterns("/*"); return fb; } }
- 스프링 부트에서 필터는 위와 같이 설정함 (FilterRegisterBean이용)
- 우선순위를 지정할 수 있음 (URL 패턴의 룰은 서블릿 URL 패턴 룰)
- 어노테이션 기반 필터도 설정 가능하지만, 순서조절이 어렵다 (@WebFilter)
* 같은 요청에 모두 같은 식별자를 자동으로 남기는 방법은 logback mdc를 검색해보자
2. 인증 체크 필터
public class LoginCheckFilter implements Filter { private static final String[] whitelist = {"/","/members/add","/login","/logout","/css/*"}; 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{ if(isLoginCheckPath(requestURI)){ HttpSession session = httpRequest.getSession(false); if(session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null){ //미인증 유저 httpResponse.sendRedirect("/login?redirectURL=" +requestURI); return; //미인증 사용자는 다음 진행 없이 끝 } } chain.doFilter(request,response); }catch(Exception e){ throw e; }finally{ //끝 } } private boolean isLoginCheckPath(String requestURI){ return !PatternMatchUtils.simpleMatch(whitelist,requestURI); } }
- whitelist : 로그인과 관계 없이 접근 할 수 있는 요소지정 (ex css리소스)
- isLoginCheckPath() : 화이트 리스트 제외한 모든 경우 인증 체크 로직 검사 (*PatternMatchUtils 좋다)
- 미인증 사용자는 다시 홈화면으로 리다이렉트 : 근데 로그인 이후에 로그인 전에 접근한 경로를 달아주자
-> 추후 로그인 성공후 redirect 경로를 로그인 전에 시도했던 경로로 보내주자!
- return -> 필터가 더 이상 진행하지 않는다 (서블릿,컨트롤러 호출 x) redirect 후에 요청 종료
2.1 필터 등록
@Bean public FilterRegisterationBean loginCheck(){ FilterRegistrationBean<Filter> fb = new FilterRegistrationBean<>(); fb.setFilter(); // 동일한 과정 fb.setOrder(); fb.addUrlPatterns(); return fb; }
2.2 컨트롤러
@PostMapping("/login") public String loginV4( @Valid @ModelAttribute LoginForm form, BindingResult bindingResult, @RequestParam(defaultValue = "/") String redirectURL, HttpServletRequest request{ if(bindingResult.hasErrors()){ return "login/loginForm"; } Member loginMember = loginService.login(form.getLgoinId(),form.getPassword); if(loginMember == null){ bindingResult.reject("loginFail","아이디 또는 비밀번호가 맞지 않습니다."); return "login/loginForm"; } HttpSession session = request.getSession(); session.setAttriute(SessionConst.LOGIN_MEMBER,loginMember); //redirectURL 적용 return "redirect:" + redirectURL; } )
> 필터 덕에 로그인 x 사용자는 특정 경로 외에는 접근 불가 -> 로그인 정책 변경시 조금만 수정하면 됨
*참고로 필터는 request,response 다음 서블릿이나 필터에게 넘겨줄 때 다른 객체로 바꿔서 넘겨줄 수 있다.
(잘 사용하지 않음 참고)
3. 스프링 인터셉터
- 서블릿 필터와 마찬가지로 웹과 관련된 공통 관심 사항을 효과적으로 해결할 수 있는 기술이다.
(스프링 MVC가 제공하는 기술임)
- 공통 관심사를 처리하지만, 순서와 범위 및 사용법이 다르다.
- 스프링 인터셉터는 디스패처 서블릿과 컨트롤러 사이에서 컨트롤러 호출 직전에 호출 됨
(스프링 MVC 기능 -> 서블릿 이후에 등장)
- 인터셉터도 URL 패턴을 적용할 수 있다. 서블릿 URL 패턴과 다름 (매우 정밀한 설정 가능)
- 인터셉터에서 적절하지 않은 요청으로 판단되면, 컨트롤러 호출 없이 끝낼 수 있다 (필터와 비슷)
- 또한 인터셉터도 체인이 자유롭다.
- 필터와 매우 비슷하지만, 더욱 정교하고 다양한 기능 지원함
3.1 인터셉터 인터페이스
- 스프링 인터셉터는 HandletInterceptor 인터페이스를 구현하면 된다.
public interface HandlerInterceptor { default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {} default void postHandle(HttpServletRequest request, HttpServletResponse response,Object handler, @Nullable ModelAndView modelAndView) throws Exception {} default void afterCompletion(HttpServletRequest request, HttpServletResponse response,Object handler, @Nullable Exception ex) throws Exception {} }
- preHandle : 컨트롤러 호출 전 (핸들러 어댑터 호출 전), 위 결과가 true면 다음 진행 false면 진행 안함
나머지 인터셉터와 핸들러 어댑터도 호출하지않음
- postHandle: 컨트롤러 호출 후 (핸들러 어댑터 호출 후)
- afterCompletion : 요청 완료 이후 (뷰가 렌더링 된 이후)
- 인터셉터에는 handler 정보와, modelAndView의 응답 정보도 확인할 수 있다.
3.2 인터셉터 예외 상황
> 예외 발생시 : postHandle 호출 x (컨트롤러 완료x) , afterCompletion은 항상 호출 (ex 파라미터로 예외 볼 수 있음)
* 예외와 무관한 처리는 afterCompletion을 사용하자
3.3 요청 로그 인터셉터
public class LofInterceptor implements HandlerInterceptor{ public static final String LOG_ID = "logId"; public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{ String requestURI = request.getRequestURI(); String uuid = UUID.randomUUID().toString(); request.setAttribute(LOG_ID, uuid); if(handler instanceof HandlerMethod){ handlerMethod hm = (HandlerMethod) handler; } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception{ log.info("postHandle [{}]", modelAndView); } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { String requestURI = requestURI(); String logId = (String)request.getAttribute(LOG_ID); log.info("RESPONSE [{}][{}]", logId, requestURI); if (ex != null) { log.error("afterCompletion error!!", ex); } } }
- request.setAttribute(LOG_ID,uuid)
서블릿 필터의 경우 지역변수로 해결이 가능하지만, 스프링 인터셉터는 호출 시점이 완전 분리
따라서 preHandle에서 따로 지정한 값을 다른 메서드로 보내려면 어딘가에 담아야함
싱글톤으로 관리됨 -> 멤버변수는 위험 -> request와 같은 객체 사용하자
*인터셉터 메서드 간 데이터 주고 받을 때는 request에 담아서 주고 받자! 시점이 다르다
if (handler instanceof HandlerMethod) { HandlerMethod hm = (HandlerMethod) handler; //호출할 컨트롤러 메서드의 모든 정보가 포함되어 있다. }
- handler의 종류를 확인하는 곳임
> HandlerMehod : 어노테이션 기반 (@Controller) 핸들러일 경우 HandlerMehod가 handler로 지정됨
> ResourceHttpRequestHandler: 정적 리소스 호출은 이 핸들러가 지정되서 넘어옴
3.4 인터셉터 등록
@Configuration public class WebConfig implements WebMvcConfigurer{ public void addInterceptors(InterceptorRegistry registry){ registry.addInterceptor(new LogInterceptor()) .order(1) .addPathPatterns("/**"); .excludePathPatterns("/css/**","/*.ico","/error"); } }
- WebMvcConfigurer를 통해 등록할 수 있다.
- 패턴을 매우 세밀하게 지정할 수 있다. (스프링 URL)
4. 인증 체크 인터셉터
public class LoginCheckInterceptor implements HandlerInterceptor{ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{ String requestURI = request.getRequestURI(); HttpSession session = request.getSession(false); if (session == null || session.getAttribute(SessionConst.LOGIN_MEMBER)== null) { response.sendRedirect("/login?redirectURL=" + requestURI); return false; } return true; } }
- 필터에 비해 매우 간결함 컨트롤러 호출 전에만 호출하면 됨
@Configuration public class WebConfig implements WebMvcConfigurer { @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" ); } //... }
- addPathPatterns와 exclude만 작성하면 쉽게 사용가능! 또한 세밀하게 패턴 조정 가능하다.
* 필터와 인터셉터는 웹과 관련된 공통 관심사를 해결하기 위한 기술임
필터에 비교해서 인터셉터가 작성하기 훨 편리함 -> 특별한 문제가 없다면 인터셉터 활용하자
'Web > Spring' 카테고리의 다른 글
Spring MVC (2) - 스프링 포맷터 (0) 2023.11.21 Spring MVC(2) - 스프링 타입 컨버터 (0) 2023.11.21 Spring MVC 2 - 검증 (2) BeanValidation (0) 2023.10.25 SpringMVC 2 - 검증 (0) 2023.10.24 Spring MVC 2 - 국제화/메시지 (0) 2023.10.24