Skip to main content

Command Palette

Search for a command to run...

[Spring] 필터와 인터셉터

Updated
•4 min read•View as Markdown
[Spring] 필터와 인터셉터
S

Nice to meet u :) Im Backend Developer

1️⃣ 필터

웹과 관련된 공통 관심사를 처리할 때 사용하며 서블릿이 제공하는 수문장이고 웹 컨테이너에서 동작한다. 디스패처 서블릿 전/후에 처리한다.

cf)애플리케이션 여러 로직에서 공통으로 관심 있는 것을 공통 관심사라고 한다.

💡
HTTP요청 → WAS → 필터1→필터2 → (디스패처)서블릿 → 컨트롤러
  • 특정 URL패턴에 적용 가능하다.

  • 필터는 체인으로 구성돼있어 중간에 필터를 추가할 수 있다.

  • 서블릿 컨테이너가 싱글톤 객체로 생성하고 관리한다.

  • HTTP요청 및 응답에 대한 처리를 수행한다.

  • MVC 전이기 때문에 예외가 발생하면 따로 처리해줘야한다.

✅ 필터 인터페이스

public interface Filter {
    default void init(FilterConfig filterConfig) throws ServletException {
    }

    void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;

    default void destroy() {
    }
}
  • init : 필터 초기화 메서드이다. 서블릿 컨테이너가 생성될 때 호출된다. 웹 컨테이너가 1회 init 메소드를 호출하여 필터 객체를 초기화하면 이후의 요청들은 doFilter를 통해 처리된다.

  • doFilter : 요청 올 때 마다 호출된다. 필터의 로직을 구현한다. doFilter의 파라미터로는 FilterChain이 있는데, FilterChain의 doFilter 통해 다음 대상으로 요청을 전달하게 된다.

  • destroy : 필터 종료 메서드이며 서블릿 컨테이너가 종료될때 호출된다. 웹 컨테이너에 의해 1번 호출되며 이후에는 이제 doFilter에 의해 처리되지 않는다.

✅ 필터 구현체 예시

@Slf4j
public class LoginCheckFilter implements Filter {
    private static final String[] whiteList={"/","/members/add","/login","/logout","/css/*"};
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) servletRequest; //ServletRequest : Http요청이 아닌 경우까지 고려해서 만든 인터페이스
        String requestURI = httpRequest.getRequestURI();
        HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
        try {
            log.info("인증 체크 로직 실행 {}", requestURI);
            HttpSession session = httpRequest.getSession(false);
            if (session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null) {
                log.info("미인증 사용자 요청 {}", requestURI);
                httpResponse.sendRedirect("/login?redirectURL=" + requestURI);
                return; //중요
            }
            filterChain.doFilter(servletRequest, servletResponse); //다음 필터가 있으면 호출, 없으면 서블릿 호출
        } catch (Exception e) {
            throw e;
        }finally {
            log.info("인증 체크 필터 종료 {}", requestURI);
        }
    }
    //화이트 리스트 일 경우 인증 체크 안함
    private boolean isLoginCheckPath(String requestURI) {
        return !PatternMatchUtils.simpleMatch(whiteList, requestURI);
    }
}

✅ 필터 등록

@Configuration
public class WebConfig {
    @Bean
    public FilterRegistrationBean logFilter() {
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new LoginCheckFilter()); //등록 필터를 지정한다.
        filterRegistrationBean.setOrder(1);//여러 필터 중 숫자가 낮을수록 먼저 동작한다.
        filterRegistrationBean.addUrlPatterns("/*"); //필터를 적용할 URL 패턴을 지정한다.
        return filterRegistrationBean;
    }
}

+)@ ServletComponentScan @ WebFilter로도 필터 등록이 가능하지만 순서 조절이 안된다.

2️⃣ 인터셉터

스프링 컨텍스트에서 동작하고 스프링 MVC가 제공하는 기술이다. 인터셉터는 스프링 MVC가 제공하는 기술이기 때문에 서블릿 뒤에 온다.

💡
HTTP요청 → WAS → 필터 → (디스패처)서블릿 → 스프링 인터셉터 → 컨트롤러
  • URL패턴 : 서블릿과 다르고 매우 정밀하게 설정할 수 있다.

  • 체인으로 구성되어 있어 중간에 추가할 수 있다.

  • 서블릿 필터보다 편리하고 정교하여 다양한 기능을 지원한다.

✅ 인터셉터 인터페이스

public interface HandlerInterceptor {
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

    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여야 다음으로 진행한다.

  • postHandle: 핸들러 어댑터 호출 후 호출된다. 즉, 컨트롤러 호출 후 실행된다. 컨트롤러 이후 처리해야하는 후처리 작업이 있을 때 사용한다.

  • afterCompletion : 뷰가 렌더링 된 이후에 호출된다. 예외가 발생해도 항상 호출된다. 사용한 리소스를 반환할 때 사용하기에 적합하다.

cf)AOP를 적용할 수 있지만 다음과 같은 이유들로 컨트롤러 호출 과정에 적용되는 부가기능들은 인터셉터를 사용하는 편이 낫다.

  1. 컨트롤러는 타입과 실행 메서드가 모두 제각각이라 포인트컷의 작성이 어렵다

  2. 컨트롤러는 파라미터나 리턴값이 일정하지 않다.

  3. AOP는 HttpServletRequest/Response 객체를 얻기 어렵지만 인터셉터에서는 파라미터로 넘어온다.

✅ 인터셉터 구현체 예시

@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(false);
        if(session == null || session.getAttribute(SessionConst.LOGIN_MEMBER)==null){
            log.info("미인증 사용자 요청");
            response.sendRedirect("/login?requestURL=" + requestURI);
            return false;
        }
        return true;
    }
}
  • 인증은 컨트롤러 호출 전에만 호출하면 돼서 preHandle만 구현하면 된다.

✅ 인터셉터 등록

@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"
               );
   }
}

서블릿 필터와 비교하면 매우 편리하다. 경로를 정교하게 지정할 수 있다.

3️⃣ 언제 사용하지

선택의 가장 큰 기준은 어떤 시점에 사용할 것인가가 중요한 것 같다.

필터는 서블릿에서 처리하는 과정의 전후를 다룰 수 있으므로 스프링과 무관하게 전역적으로 처리하는 작업들을 수행할 때 사용할 수 있을 것이다. 예를 들어 모든 요청의 로그를 남기거나, 인증 및 권한 부여, 캐싱일때 할 수 있다.

인터셉터는 스프링 내부에서 컨트롤러가 처리하는 과정의 전후를 다룰 수 있으므로 컨트롤러에 넘겨주는 데이터를 가공하거나 처리할 때 사용할 수 있다. 예를 들어 토큰 유효성을 검사하거나, 인증 및 권한 부여, 캐싱일때 할 수 있다.

참고자료

https://dd-developer.tistory.com/111

https://mangkyu.tistory.com/173

More from this blog

[Spring] N+1문제 발생과 분석

✍️ 작성하게 된 이유 옷을 관리하는 서비스를 개발하면서 Cloth 엔티티와 그에 연관된 ClothWithAttributes, Attribute 데이터를 함께 조회하는 기능이 필요했다.그런데 연관 데이터를 조회할 때마다 쿼리가 폭발적으로 증가(N+1 문제) 하며, 성능이 급격히 저하되는 상황을 마주하게 되었다. Spring JPA의 대표적인 문제로 N+1임을 알고있었지만, 해결하는 방법은 Fetch Join밖에 몰랐다. 지연로딩되는 필드를 엔티...

Sep 17, 20256 min read
[Spring] N+1문제 발생과 분석

데이터베이스 기본 개념 정리

1️⃣ 데이터베이스(DB) & DBMS DB (Database): 일정한 규칙(스키마)에 따라 구조화되어 저장된 데이터의 집합. DBMS (Database Management System): DB를 제어/관리하는 시스템 소프트웨어. 특징: 실시간 접근 가능, 동시 공유 가능. 구조: 데이터베이스 → DBMS → 응용 프로그램 → 사용자 2️⃣ 엔티티(Entity) & 릴레이션(Relation) 엔티티: 여러 속성을 가진 "개체"...

Aug 5, 20252 min read
데이터베이스 기본 개념 정리

[Project] 날씨에 맞는 옷 추천 서비스 : 지그재그 크롤링 여정 기록 (1) ChromeDriver를 EC2에 설치하기

✍️ 작성하게 된 이유 무신사, 29cm는 Jsoup으로 충분히 크롤링이 가능했기 때문에, ZigZag도 당연히 Jsoup으로 처리될 것이라 생각했다. 무신사, 29cm와 마찬가지로 필요한 데이터는 모두 <script> 태그 안에 들어있었다. 하지만… 예상은 보기 좋게 빗나갔다. 🧪 현상 ✅ 로컬 크롤링 → 정상 작동 Jsoup으로 script 태그 내에서 대표 이미지와 상품명을 잘 추출 로컬 환경에서는 아무 문제 없이 작동 ❌ A...

Jul 30, 20253 min read
[Project] 날씨에 맞는 옷 추천 서비스 : 지그재그 크롤링 여정 기록 (1) ChromeDriver를 EC2에 설치하기

[Project] 날씨에 맞는 옷 추천 프로젝트: Selenium은 정말 필요한 선택이었을까? - 크롤링 삽질 기록

✍️ 작성하게 된 이유 날씨에 따라 옷을 추천해주는 서비스를 만들면서, 사용자가 입력한 구매 링크에서 옷 정보( 대표이미지, 상품명 )를 불러오는 기능이 필요했다. 처음에 해당 페이지를 동적 페이지로 판단했고, 자연스럽게 Selenium을 도입했다. 하지만 이 결정이 과연 최선이었는지는 수많은 시행착오 끝에야 알 수 있었다. 🕸️ Selenium을 선택한 이유 동적 페이지는 Jsoup으로 크롤링이 어렵다는 인식으로 처음부터 Selenium을 ...

Jul 28, 20254 min read
[Project] 날씨에 맞는 옷 추천 프로젝트: Selenium은 정말 필요한 선택이었을까? - 크롤링 삽질 기록

Soyulia's Blog

49 posts