서블릿에서 제공하는 기능으로, Dispatcher Servlet으로 요청이 전달되기 전후에 URL패턴에 맞는 모든 요청에 대한 부가작업을 처리할 수 있는 기능을 제공한다.
로그인 : HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러
비 로그인 : HTTP 요청 -> WAS -> 필터(적절하지 않은 요청이라 판단, 서블릿 호출X)
HTTP 요청 -> WAS -> 필터1 -> 필터2 -> 필터3 -> 서블릿 -> 컨트롤러
저번에 Oauth2를 이용하여 sns로그인 기능을 구현해 보았을 때 필터 체인이 나와서 무엇인지 몰랐는데, 필터는 체인으로 구성되어 여러가지 필터를 추가할 수 있다. 필터 순서에 따라서 로그인 필터를 먼저 적용하고, 로그를 남기는 필터를 그 다음에 적용하는 등으로 필터 체인을 구성할 수 있다.
그럼 로그를 남기는 필터를 생성해보자!!
@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");
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");
}
}
@Configuration
public class WebConfig {
@Bean
public FilterRegistrationBean logFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LogFilter());
filterRegistrationBean.setOrder(1);
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
}
이렇게 등록까지 마치고 상품 등록을 시도해보면
콘솔에 로그가 찍혀 있는 것을 확인할 수 있다.
@Slf4j
public class LoginCheckFilter implements Filter {
// 로그인이 필요없는 페이지
private static final String[] whiteList = {"/", "/members/add", "/login", "/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);
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);
}
}
@Bean
public FilterRegistrationBean loginCheckFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LoginCheckFilter());
filterRegistrationBean.setOrder(2);
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
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.getLoginId(), form.getPassword());
if (loginMember == null) {
bindingResult.reject("loginFail", "아이디 또는 비밀번호가 맞지 않습니다.");
return "login/loginForm";
}
//로그인 성공 처리
// getSession(true) - 있으면 기존 세션 반환, 없으면 생성해서 반환
// getSession(false) - 있으면 기존 세션 반환, 없으면 null반환
HttpSession session = request.getSession();
// 세션에 로그인 회원 정보 보관
session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);
return "redirect:" + redirectURL;
이것으로 필터에 대해서 알아보고 구현도 해보았다. 다음 포스팅에서는 스프링에서 제공하는 인터셉터에 대해 알아보자!!!