Filter 에 대해 간단히 알아 보았으니 이제 본격적으로 모든 페이지들에 대해 로그인 체크를 하는 필터를 개발해보자.
Filter 는 인터페이스였으므로 개발자가 사용 목적에 맞춰 구현을 해줘야한다.
LoginCheckFilter 라는 이름으로 Filter를 구현해주자.
package hello.login.web.filter;
import hello.login.web.SessionConst;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.PatternMatchUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@Slf4j
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;
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);
}
}
request 를 검사하여 로그인 사용자인지 비 로그인 사용자인지 필터링을 수행한다.
만일 비로그인 사용자일 경우 response 의 sendRedirect() 를 이용해 로그인 페이지로 리다이렉트 시켜버리고 종료된다.
로그인 사용자일 경우엔 chain.doFilter() 가 동작해 다음 filter로 이동한다.
whitelist 에 담긴 URL 들은 로그인을 하지 않아도 들어갈 수 있는 목록들이다.
우리가 구현한 filter 객체를 스프링에서 사용하기 위해선 당연히 @Bean 으로 등록을 해줘야한다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
...
@Bean
public FilterRegistrationBean loginCheckFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LoginCheckFilter());
filterRegistrationBean.setOrder(2);
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
}
우리가 만들어준 필터를 세팅해준다.
필터도 여러가지를 등록할 수 있기 때문에 뭐가 먼저 수행될지 순서를 정해줘야한다. 이 경우에 우선순위는 2번째가 된다.
등록할 필터를 어떤 URL(페이지)에 적용할지 추가해준 메서드이다. 로그인의 경우 필터를 해야할 페이지가 압도적으로 많으므로 루트경로를 집어넣어준다.
대신 LoginFilterCheck 에서 whitelist를 이용해 로그인이 필요없는 URL 에 대해 예외처리를 해놓았으므로 괜찮다.
@Component 를 LoginCheckFilter 에 그냥 달아줘서 자동으로 컴포넌트스캔이 되도록 해도 되지만 FilterRegistrationBean 을 쓰는것이 스프링이 제공해주는 수많은 다양한 기능을 사용 할 수 있기 때문에 후자 방식을 선택했다.
앞에서 필터를 구현할때 비로그인 사용자일경우 redirect 될 페이지 URL을 쿼리 파라미터로 넘겨주었다.
그렇기 때문에 GET /login 컨트롤러 메서드 또한 쿼리 파라미터를 전달 받을 수 있도록 수정해줘야한다.
@PostMapping("/login")
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";
}
//로그인 성공 처리
//세션이 있으면 있는 세션 반환, 없으면 신규 세션을 생성
HttpSession session = request.getSession();
//세션에 로그인 회원 정보 보관
session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);
return "redirect:" + redirectURL;
}
@RequestParam() 을 추가 해주었다.
본 포스트는
김영한의 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 강의 를 보고 정리했습니다.