Servlet Filter, Spring InterCeptor

yookyungmin·2023년 6월 4일
0
  • 웹과 관련된 공통 관심사는 AOP보다 서블릿 필터 또는 인터셉터로 해결하는 것이 좋다

필터의 흐름

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");
    }
}
모든 요청을 로그로 남기는 필터이다.

webconfig - 필터 설정

    @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
    }
}

webconfig 설정

   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만 구현하면 된다.

webconfig 설정

@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"
                ); //인터셉터 적용 안할 곳
    }

정리를 하자면
서블릿 필터'와 '스프링 인터셉터'는 웹과 관련된 공통 관심사를 해결하기 위한 기술이다.
스프링 인터셉터는 필터보다 비교적 편하다
특별한 문제가 없다면 인터셉터를 사용하는 것이 좋다.

0개의 댓글