필터를 적용하면 필터가 호출된 이후 서블릿이 호출된다.
(여기서 서블릿은 스프링의 경우 디스패처 서블릿을 의미한다고 생각하면 된다.)
인터셉터를 적용하면 디스패처 서블릿과 컨트롤러 사이에서 컨트롤러 호출 직전에 호출된다.
필터는 서블릿 호출전에 인터셉터는 서블릿 호출 이후 호출되기에 인터셉터는 서블릿에서 예외가 발생한다면 호출되지 않는다.
Dispatcher Servlet
에 요청이 전달되기 전 / 후에 url 패턴에 맞는 모든 요청에 대해 부가 작업을 처리할 수 있는 기능을 제공한다.init()
을 호출하여 필터 객체를 초기화하면 이후 요청들은 doFilter()
를 통해 처리된다.doFilter
의 파라미터로 FilterChain
이 있는데, FilterChain
의 doFilter
통해 다음 대상으로 요청을 전달할 수 있게 된다.chain.doFilter()
전, 후에 우리가 필요한 처리 과정을 넣어줌으로써 원하는 처리를 진행할 수 있다.destroy()
를 호출하여 필터 객체를 종료하면 이후에는 doFilter
에 의해 처리되지 않는다.doFilter
가 호출된다. ServletRequest request
는 HTTP 요청이 아닌 경우도 고려해서 만든 인터페이스이다. HTTP를 사용하면 HttpServletRequest
로 명시적 형변환을 한 뒤 사용하면 된다. public class LogFilter implements Filter{
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest)request;
String requestURI = httpServletRequest.getRequestURI();
// doFilter 이전
System.out.println("requestURI : " + requestURI);
chain.doFilter(request,response);
// doFilter 이후
System.out.println("responseURI : " + requestURI);
}
}
Configuration
을 만들어 해당 필터를 등록한다. // @Bean
// postconstruct는 스프링부트에서 제공해주는 어노테이션이였어서 Bean등록도 다 처리가 됬었다
public FilterRegistrationBean logFilter() {
FilterRegistrationBean<Filter> fiterRegistrationBean
= new FilterRegistrationBean<Filter>();
fiterRegistrationBean.setFilter(new LogFilter()); // LogFilter 등록
fiterRegistrationBean.setOrder(1);
fiterRegistrationBean.addUrlPatterns("/*"); // 모든 url 다 적용
return fiterRegistrationBean;
}
}
whitelist
로 지정한 경로를 제외하고는 모두 로그인 상태를 검사 후 페이지 접근 여부를 결정한다.
private static final String[] whitelist = {"/", "/members/add", ...};
isLoginCheckPath(String requestURI)
requestURI
가 화이트리스트와 일치하는지 검사한다. 이때 PatternMatchUtils
라는 정적 헬퍼 클래스를 이용하여 쉽게 경로 검사가 가능하다. httpResponse.sendRedirect("/login?redirectURL=" + requestURI);
redirectURL
이라고 queryString
을 작성하고 있다.
- 로그인 하지 않고 items 입력 후 이동
- 사용자가 넘겨준
uri
를 파라미터 값으로 기억
redirect
를 사용했기에 redirect
가 응답으로 적용되고 요청이 끝난다. 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;
System.out.println("인증 체크 필터 시작 ");
if(isLoginCheckpath(requestURI)) {
System.out.println("인증체크 로직 실행 : " + requestURI);
HttpSession session = httpRequest.getSession(false);
if(session == null
|| session.getAttribute(SessionConst.LOGIN_MEMBER) == null) {
System.out.println("미 인증 사용자 요청");
// 로그인으로 redirect
// 사용자가 넘겨준 uri를 파라미터 값으로 기억
httpResponse.sendRedirect("/login?redirectURL=" + requestURI);
// 미인증 사용자는 다음으로 진행하지 않고 끝낸다.
return;
}
}
// 다음 단게로 넘어간다.
chain.doFilter(request, response);
}
/*
* 화이트 리스트의 경우 인증 체크 X
* PatternMatchUtils : 파라미터 문자열이 특정 패턴에 매칭되는지를 검사함.
*/
private boolean isLoginCheckpath(String requestURI) {
return !PatternMatchUtils.simpleMatch(whitelist, requestURI);
}
}
@Bean
public FilterRegistrationBean loginCheckFilter() {
FilterRegistrationBean<Filter> fiterRegistrationBean
= new FilterRegistrationBean<Filter>();
fiterRegistrationBean.setFilter(new LoginCheckFilter()); // LoginCheckFilter 등록
fiterRegistrationBean.setOrder(2);
fiterRegistrationBean.addUrlPatterns("/*"); // 모든 url 다 적용
return fiterRegistrationBean;
}
로그인이 성공했을 경우 redirectURL
이라는 @RequestParam
을 조회해 만약 다른 페이지로 접근을 시도하다 로그인 페이지로 온 경우 다시 되돌아가기위해 사용한다.
return "redirect:" + redirectURL;
/
/items
@PostMapping("/login")
public String loginV3(@ModelAttribute LoginForm form, Model model,
RedirectAttributes redirectAttributes, HttpServletRequest request,
@RequestParam(defaultValue = "/")String redirectURL) {
Member loginMember = loginService.login(form.getLoginId(), form.getPassword());
if(loginMember == null) {
// 로그인 실패
model.addAttribute("msg", "로그인 실패");
return "login/loginForm";
}
// 로그인 성공
HttpSession session = request.getSession();
// 세션에 로그인 회원 정보 보관
session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);
redirectAttributes.addFlashAttribute("msg","로그인성공");
// 파라미터로 넘어온 값이 없으면 /
// 파라미터로 넘어온 값이 있으면 /items
return "redirect:" + redirectURL;
}
public class LogInterceptor implements HandlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String requestURI = request.getRequestURI();
System.out.println("[interceptor requestURI : " + requestURI);
return true; // false -> 진행 X
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("[interceptor] postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("[interceptor] afterCompletion");
}
}
생성한 스프링 인터셉터(LogInterceptor)를 설정에 등록
WebMvcConfigurer
인터페이스를 구현하여 addInterceptor
메서드를 재정의해서 인터셉터 등록이 가능하다.
addInterceptor : 인터셉터를 등록한다.
order(1) : 인터셉터의 호출 순서를 지정하며 낮을 수록 먼저 호출된다.
addPathPatterns("/") : 인터셉터를 적용할 URL 패턴을 지정한다.
excludePathPatterns("/css/", "/*.ico", "/error") : 인터셉터에서 제외할 패턴을 지정한다.
addPathPatterns("/sub1/test1", "/sub1/test2")
/* -> /sub1/*
sub1/**
@Component
public class WebConfig implements WebMvcConfigurer{
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns("/error");
registry.addInterceptor(new LoginCheckInterceptor())
.order(2)
.addPathPatterns("/**") // 모든 경로 전체
.excludePathPatterns("/", "/members/add", "/login", "/logout","/css/**");
}
public class LoginCheckInterceptor implements HandlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String requestURI = request.getRequestURI();
System.out.println("[interceptor] : " + requestURI);
HttpSession session = request.getSession(false);
if(session == null || session.getAttribute(SessionConst.LOGIN_MEMBER)== null) {
System.out.println("[미인증 사용자 요청]");
// 로그인으로 redirect
response.sendRedirect("/login?redirectURL = "+ requestURI);
return false;
}
return true;
}
}
필터와 인터셉터 모두 비즈니스 로직과 분리되어 특정 요구사항(보안, 인증, 인코딩 등)을 만족시켜야 할 때 적용한다.
필터(Filter)는 특정 요청과 컨트롤러에 관계없이 전역적으로 처리해야 하는 작업이나
웹 어플리케이션에 전반적으로 사용되는 기능을 구현할 때 적용하고,
인터셉터(Interceptor)는 클라이언트의 요청과 관련된 작업에 대해 추가적인 요구사항을 만족해야 할 때 적용한다.
https://dev-coco.tistory.com/173#--%--%ED%--%--%ED%--%B--Filter-
https://catsbi.oopy.io/9ed2ec2b-b8f3-43f7-99fa-32f69f059171