-
서블릿 필터스프링/스프링 MVC 패턴 2023. 3. 1. 15:42
로그인이 된 사용자만 상품 관리 페이지에 들어갈 수 있어야 하는데 로그인을 하지 않은 사용자가 URL을 직접 호출하면 상품 관리 화면에 들어가서 기능들을 사용할 수 있다.
웹에 관련된 공통 관심사를 처리할 때에는 서블릿 필터나 스프링 인터셉터를 사용하는 것이 좋다. 이 글에서는 서블릿 필터에 대해 배우도록 하겠다.
필터 흐름
HTTP 요청 ---> WAS ---> 필터 ---> 서블릿 ---> 컨트롤러
필터를 적용하면 피터가 호출된 다음에 서블릿이 호출되며 특정 URL 패턴에 적용할 수 있다.
로그인 한 사용자는 필터, 서블릿을 건너서 컨트롤러까지 호출하는데 로그인을 하지 않은 사용자는 필터에서 요청을 끝내버릴 수 있다. 로그인 여부 등을 체크하기에 좋은 기능이다.
필터는 체인으로 구성되기 때문에 필터를 여러 개 지정할 수 있다.
LogFilter 클래스
@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; // Http만 나오도록 다운캐스팅 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"); } }
LogFilter 클래스이다. 필터가 시작되고 끝났을 때의 로그를 출력해주는 코드이다.
LoginCheckFilter 클래스
@Slf4j public class LoginCheckFilter implements Filter { private static final String[] whitelist = {"/", "/members/add", "/login", "/logout","/css/*"}; // whitelist는 인증 체크 필요 x @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); } }
다음으로 로그인체크 필터 클래스이다. init과 destroy 메서드를 오버라이딩 하지 않았는데, 그 이유는 default값을 서블릿에서 지원하기 때문에 없어도 되어서 삭제했다. whitelist라는 목록이 있는데, 우리가 상품 관리를 하는 페이지는 로그인을 해야지 접근가능하지만 로그인 페이지와 회원가입 홈 페이지는 세션이 없더라도 접근이 가능해야하기 때문에 화이트리스트를 따로 설정해두어서 세션이 없어도 접근 가능하게 만들었다.
try catch 문을 보면 session이 null이면 로그인 페이지로 리다이렉트 한다. httpResponse.sendRedirect("/login?redirectURL=" + requestURI);로 리다이렉트 하고 return;을 통해 더 이상 진행하지 않고 나가게 된다.
session이 올바르게 진입했으면 chain.doFilter로 다음 chian으로 가거나 서블릿으로 향하는데 여기서는 서블릿으로 향할 목적이라서 chain.doFilter(request, response);를 입력했다.
WebConfig 클래스
@Configuration public class WebConfig { @Bean public FilterRegistrationBean logFilter() { FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>(); filterRegistrationBean.setFilter(new LogFilter()); filterRegistrationBean.setOrder(1); // 체인 1번 filterRegistrationBean.addUrlPatterns("/*"); // 모든 Url에 다 적용 return filterRegistrationBean; } @Bean public FilterRegistrationBean loginCheckFilter() { FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>(); filterRegistrationBean.setFilter(new LoginCheckFilter()); filterRegistrationBean.setOrder(2); // 체인 2번 filterRegistrationBean.addUrlPatterns("/*"); return filterRegistrationBean; }
우리가 만든 필터들이 적용될 수 있도록 하는 @Configuraiton이다. logFilter를 첫 번째 체인으로, loginCheckFilter를 두 번째 체인으로 지정했다.LoginController@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; }
로그인 컨트롤러에도 변화가 있었는데, 아래 return을 보면 redirect 뒤에 redirectURL이 있다. 이 뜻은 만약 사용자가 상품 등록을 하다 로그인이 안되어있어서 로그인을 했는데 홈 화면 페이지로 이동하면 불편한 서비스이니까 로그인을 한 뒤에도 상품 등록 페이지로 연결할 수 있도록 하는 코드이다.
위에 url을 보면 redirectURL=/items로 남겨져 있다.
LoginController에서 @RequestParam 으로 redirectURL을 받고 있어서 로그인을 하게 되면
홈 화면이 아니라 바로 items 화면으로 넘어가게 된다.
'스프링 > 스프링 MVC 패턴' 카테고리의 다른 글
스프링 부트의 오류 페이지 (0) 2023.03.03 스프링 인터셉터 (서블릿 필터 상위 기능) (0) 2023.03.01 서블릿 Http 세션 (0) 2023.02.28 로그인 페이지 만들기 (0) 2023.02.28 상품 등록 전 로그인하기 - 회원가입 페이지 만들기 (0) 2023.02.28