티스토리 뷰
스프링 프레임워크에서 필터와 인터셉터는 웹 애플리케이션의 요청 및 응답을 처리하기 전후에 실행되는 컴포넌트이다.
스프링에서 컨트롤러로 요청이 들어오기 전에 처리해야 하는 작업이 있다. 애플리케이션 여러 로직에서 공통으로 관심이 있는 있는 것을 공통 관심사(cross-cutting concern)라고 하는데 대표적으로 인증, 인가, 로깅 등이 있고 주로 필터와 인터셉터를 통해 해당 기능을 수행한다.
필터와 인터셉터 두 컴포넌트 모두 비슷한 목적을 가지고 있지만, 사용되는 시점과 적용되는 방식에 차이가 있다.
필터(Filter)
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() {}
}
필터는 Servlet 컨테이너 레벨에서 작동하며, HTTP 요청 및 응답에 대한 처리를 수행한다. 스프링에서 필터 인터페이스의 패키지를 보면
package javax.servlet;
javax.servlet 에 위치하고 있다. 즉, 스프링 프레임워크와는 무관하게 서블릿 컨테이너 레벨에서 작동한다는 것이다.
Filter 인터페이스 첫 줄의 설명을 읽어보면 (한국어로 번역했다..)
필터(Filter)는 서블릿(Servlet)이나 정적인 콘텐츠(static content)에 대한 요청이나 응답, 혹은 둘 다에 대한 필터링 작업을 수행하는 객체이다.
즉, 필터는 서블릿이 지원하는 수문장이고, 필터를 적용하면 필터가 호출 된 다음에 서블릿이 호출된다. 그래서 만약 모든 고객의 요청 로그를 남기는 요구사항이 있다면 필터를 사용하면 된다.
필터는 체인으로 구성되는데, 중간에 필터를 자유롭게 추가할 수 있다. 위에서 작성한 Filter 인터페이스를 보면 doFilter 라는 메서드를 확인할 수 있다.
init: 필터 객체를 초기화하고 서비스에 추가하는 메서드
doFilter: 처리를 구현해서 동작한 후, 파라미터로 받은 FilterChain을 사용해서 다음 대상으로 요청을 넘겨줌
destroy: 필터를 제거하고 자원을 반환하기 위한 메서드
여기서 doFilter의 첫 번째 파라미터가 ServletRequest임을 확인할 수 있는데, 이는 Http 요청이 아닌 경우까지 고려해서 만들었다고 한다. HttpServletRequest를 사용하려면 다운캐스팅해서 사용하면 된다.
필터를 커스텀해서 사용하는 방법은 Filter 인터페이스를 적절히 구현한 후
public class CustomFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("log: filter doFilter Method");
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
FilterRegistrationBean을 이용해 filter를 bean으로 등록하면 된다. 필터를 bean으로 등록할 때 순서와 url 패턴도 설정할 수 있다.
@Configuration
public class WebConfig {
@Bean
public FilterRegistrationBean registerFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new
FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new CustomFilter());
filterRegistrationBean.setOrder(1);
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
}
인터셉터(Interceptor)
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 {
}
}
인터셉터는 스프링 프레임워크 레벨에서 작동하며, 주로 컨트롤러의 요청 처리 전후에 실행된다. 인터셉터도 필터와 같이 웹과 관련된 공통 관심 사항을 효과적으로 해결할 수 있는 기술이지만, 적용되는 순서와 범위, 그리고 사용방법이 다르다.
인터셉터는 HandlerInterceptor 인터페이스를 구현해서 사용할 수 있다. HandlerInterceptor의 패키지를 보면
package org.springframework.web.servlet;
org.springframework.web.servlet에 위치하고 있다. 즉 스프링 컨텍스트 내부에서 작동한다는 것이다. DispatcherServlet은 내부적으로 핸들러 매핑을 통해 적절한 컨트롤러를 찾도록 요청하는데 그 결과로 HandlerExecutionChain을 반환한다.
실행 체인은 스프링 컨테이너에 인터셉터가 등록되어 있다면 인터셉터들을 순차적으로 거친 후에 컨트롤러를 실행하도록 한다.
HandlerInterceptor 인터페이스에는 preHandle, postHandle, afterCompletion 메서드가 존재한다.
preHandle: 반환 값으로 true와 false를 반환하는데, true인 경우 다음 인터셉터를 호출하고 false인 경우 요청을 종료
postHandle: 컨트롤러가 호출된 직후에 실행
afterCompletion: 요청이 완료된(뷰 응답) 이후에 실행
@Component
public class LoginInterceptor implements HandlerInterceptor {
private static final String AUTHORIZATION = "Authorization";
private static final String MEMBER_ERROR_MESSAGE = "일치하는 회원이 없습니다.";
private static final String PASSWORD_ERROR_MESSAGE = "비밀번호가 일치하지 않습니다.";
private final BasicAuthorizationExtractor extractor;
private final MemberDao memberDao;
public LoginInterceptor(BasicAuthorizationExtractor extractor, MemberDao memberDao) {
this.extractor = extractor;
this.memberDao = memberDao;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
MemberDto memberDto = extractor.extract(request.getHeader(AUTHORIZATION));
validate(memberDto);
return true;
}
private void validate(MemberDto memberDto) {
MemberEntity findMember = memberDao.findByEmail(memberDto.getEmail())
.orElseThrow(() -> new MemberNotFoundException(MEMBER_ERROR_MESSAGE));
if (findMember.hasDifferPassword(memberDto.getPassword())) {
throw new MemberNotFoundException(PASSWORD_ERROR_MESSAGE);
}
}
}
이번 장바구니 미션에 사용한 로그인 요청을 처리하는 인터셉터이다. preHandle 메서드에서 httpRequest 헤더의 Authorization 값을 확인한 후, 회원인지 아닌지 판단한다.
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
private final LoginInterceptor loginInterceptor;
public WebMvcConfiguration(LoginInterceptor loginInterceptor) {
this.loginInterceptor = loginInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/carts/**");
}
}
인터셉터를 구현한 후 WebMvcConfigure의 addInterceptos 메서드를 오버라이드 해 registry에 등록하면 된다. 필터와 비교해보면 인터셉터는 addPathPatterns , excludePathPatterns 로 정밀하게 URL 패턴을 지정할 수 있다.
이렇게 되면 인터셉터를 이용해 컨트롤러에 요청이 도달하기 전 인증된 사용자를 판단할 수 있다.
차이점
그렇다면 필터와 인터셉터 둘 중 어떤 걸 사용해야 할까?
선택의 가장 큰 기준은 어떤 시점에 사용할 것인가가 중요한 것 같다.
필터는 서블릿 컨텍스트에 등록하고, 인터셉터는 스프링 컨텍스트에 등록한다.
필터는 서블릿에서 처리하는 과정의 전후를 다룰 수 있으므로 스프링과 무관하게 전역적으로 처리하는 작업들을 수행할 때 사용할 수 있을 것이다. 예를 들어 모든 요청의 로그를 남기거나, 인증 및 권한 부여, 캐싱을 할 수 있다.
인터셉터는 스프링 내부에서 컨트롤러가 처리하는 과정의 전후를 다룰 수 있으므로 컨트롤러에 넘겨주는 데이터를 가공하거나 처리할 때 사용할 수 있다. 예를 들어 토큰 유효성을 검사하거나, 인증 및 권한 부여, 캐싱을 할 수 있다.
이렇게 적고 보니 필터와 인터셉터는 거의 유사한 기능을 한다. 다만 어떤 시점에 사용할 것인가에서 큰 차이가 발생한다. 또한 필터의 경우 요청 파라미터를 ServletRequest로 받아 인터셉터보다 폭 넓은 요청을 처리할 수 있다는 장점이 있고, 인터셉터의 경우 필터보다 url을 좀 더 정교하게 다룰 수 있는 장점이 있다.
'Spring' 카테고리의 다른 글
[JPA] @NotNull과 nullable = false는 어떤 차이가 있을까? (0) | 2023.07.12 |
---|---|
[Spring] Service에서 Service를 의존할까 Repository를 의존할까 (4) | 2023.06.04 |
[Spring] Mock 객체의 리턴값이 왜 null이 나올까? (1) | 2023.04.23 |
[Querydsl] Unable to load class 'com.mysema.codegen.model.Type' 에러 해결 방법 (0) | 2023.01.12 |
[Spring] JPQL로 단일 Entity 찾을 시 에러 (No entity found for query) (0) | 2022.06.26 |
- Total
- Today
- Yesterday
- ZNS
- CI/CD
- Spring
- 스프링 부트
- 파이썬
- 피움
- 팀프로젝트
- 우테코 회고
- 스프링MVC
- 피움 6주차 회고
- ZNS SSD
- 5주차 회고
- 2차 데모데이
- 알림개선기
- 백준
- 프로젝트
- java
- dm-zoned 코드분석
- 환경 별 로깅 전략 분리
- 회고
- 스프링 프레임워크
- 우테코
- jpa
- 3차 데모데이
- 스프링 Logback
- 네트워크
- 8주차 회고
- 알림기능개선기
- dm-zoned
- 런칭 페스티벌
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |