세션을 가지고 로그인 기능을 구현했지만 아직도 여전히 문제가 많이 있었다.
로그인을 하지 않더라도 url경로만 알고있다면 접근이 가능했다.
그래서 이러한 문제를 없애고자 서블릿은 필터, 스프링은 인터셉터를 제공한다.
AOP를 사용할 수 있지만 웹과 관련된 공통 관심사 처리같은 경우는 HTTP 정보들이 필요할 수 있어 필터나 인터셉터를 사용하는 것이 더 좋다.
필터를 사용하면 다음과 같은 프로세스를 거친다.
참고로 필터는 여러 개 추가가 가능하다. = 필터 체인
public interface Filter {
default void init(FilterConfig filterConfig) throws ServletException {}
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException;
default void destroy() {
}
}
주의할 점이 doFilter()에 로직을 작성하고 doFilter()를 호출해줘야한다.
호출해줘야 다음 필터가 있으면 필터 호출, 없으면 서블릿을 호출해주기 때문이다.
만약 호출해주지 않으면 다음 단계로 넘어가지 않고 코드가 끝난다.
그 다음 필터를 빈으로 등록해줘야 사용할 수 있는데
1. @Component
참고로 init()과 destroy()는 default여서 구현을 안해도 상관없다.
@Slf4j
public class LoginCheckFilter implements Filter {
private static final String[] whiteList = {"/", "/add/user", "/login","/logout", "/css/*"};
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("loginCheckFilter start");
}
@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 {
if (!isLoginCheckPath(requestURI)) {
// 화이트리스트에 속하지 않으면 로그인 인증 필터 적용
HttpSession session = httpRequest.getSession(false);
if (session == null || session.getAttribute("loginUser") == null) {
// 로그인 성공 시 다시 본 페이지로 돌아올 수 있도록 redirectURL을 추가함.
httpResponse.sendRedirect("/login?redirectURL"+requestURI);
return;
}
}
chain.doFilter(request, response);
}catch(Exception e) {
throw e;
}
}
private boolean isLoginCheckPath(String requestURI) {
return PatternMatchUtils.simpleMatch(whiteList, requestURI);
}
@Override
public void destroy() {
log.info("loginCheckFilter end");
}
}
@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 {
if (!isLoginCheckPath(requestURI)) {
// 화이트리스트에 속하지 않으면 로그인 인증 필터 적용
HttpSession session = httpRequest.getSession(false);
if (session == null || session.getAttribute("loginUser") == null) {
// 로그인 성공 시 다시 본 페이지로 돌아올 수 있도록 redirectURL을 추가함.
httpResponse.sendRedirect("/login?redirectURL="+requestURI);
return;
}
}
chain.doFilter(request, response);
}catch(Exception e) {
throw e;
}
}
@Component
public class LoginCheckFilter implements Filter {
...
}
@Configuration
public class WebConfig {
@Bean
public FilterRegistrationBean loginCheckFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new LoginCheckFilter());
filterRegistrationBean.setOrder(1); // 순서 지정
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
}
해당 프로젝트에선 @Component 등록 방식을 택했다.
마지막으로 로그인 성공 시 요청URL로 다시 redirect하게 해야하니 login 성공 시 컨트롤러를 수정야한다.
public String login(@Validated @ModelAttribute Login login, BindingResult bindingResult,
@RequestParam(defaultValue = "/") String redirectURL,
HttpServletRequest request){
...
...
// 로그인 성공 시
return "redirect:"+redirectURI;
}
서블릿 필터가 아닌 스프링의 인터셉터를 적용시켜보자.
스프링 인터셉터는 서블릿 필터보다 정교한 URL 설정도 가능하고 더 편리하다.
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {}
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()만 구현하면 끝이다.
인터셉텨가 편리한 이유
1. HTTPServletRequest를 다운캐스팅할 필요가 없다.
2. 인터셉터를 적용한 URL을 쉽게 등록할 수 있다.
구현을 해보자.
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("loginUser") == null){
response.sendRedirect("/login?redirectURL="+requestURI);
return false;
}
return true;
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginCheckInterceptor())
.order(1)
.addPathPatterns("/**") // 인터셉어틔 모든 경로 표현은 /**로 한다.
.excludePathPatterns("/", "/add/user", "/login","/logout", "/css/*"); //화이트리스트
}
}