ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링 인터셉터 (서블릿 필터 상위 기능)
    스프링/스프링 MVC 패턴 2023. 3. 1. 16:44

    스프링 인터셉터는 서블릿 필터와 비슷하지만 서블릿 필터보다 더 많은 기능을 지원하고 정교하게 사용할 수 있다.

     

     

    스프링 인터셉터 흐름

    HTTP 요청   --->   WAS   --->   필터   --->   서블릿   --->   스프링 인터셉터   --->   컨트롤러

     

     

    스프링 인터셉터 또한 체인 형식이라 인터셉터를 여러개 넣을수도 있다.

     

     

    • preHandle : 컨트롤러 요청 전에 호출

    - preHandle의 응답값이 true면 다음으로 진행하고 false면 인터셉트, 핸들러 어댑터 전부 호출하지 않는다.

    • postHandle : 컨트롤러 요청 후에 호출
    • afterCompletion : http 최종 요청 후에 호출 (뷰가 렌더링 된 이후에 호출)

     

    컨트롤러에서 예외가 발생하면 postHandle은 호출되지 않는다. 하지만 afterCompletion은 항상 실행되며 ex 예외를 파라미터로 받아서 어떤 예외가 발생했는지 로그로 출력할 수 있다.

     

     

    인터셉터는 스프링 MVC 구조에 특화된 필터 기능을 제공하기에 특별히 필터를 사용해야 하는 상황이 아니라면 인터셉터를 사용하는 것이 편리하다.

     

     

    스프링 인터셉터는 위에서 말한 3가지의 메서드를 오버라이딩 해야한다.

    preHandle 메서드

    @Slf4j
    public class LogInterceptor implements HandlerInterceptor {
    
        public static final String LOG_ID = "logId";
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
            String requestURI = request.getRequestURI();
            String uuid = UUID.randomUUID().toString();
    
            request.setAttribute(LOG_ID, uuid); // 싱글톤이라 afterCompletion에서 사용하려면 request에 담아서 사용
    
            // @RequestMapping: HandlerMethod
            // 정적 리소스: ResourceHttpRequestHandler
            if (handler instanceof HandlerMethod) {
                HandlerMethod hm = (HandlerMethod) handler;// 호출할 컨트롤러 메서드의 모든 정보가 포함되어 있다.
            }
    
            log.info("REQUEST [{}][{}][{}]", uuid, requestURI, handler);
            return true;
    
        }

    preHandle 메서드는 컨트롤러 호출 전에 호출되는 메서드이다. uuid를 afterCompletion에서 사용해야했는데 싱글톤이라 위에서 선언해서 사용하면 안되어서 request에 담아서 다른 공간에서도 getAttribute로 사용할 수 있도록 했다.

     

    postHandle과 afterCompletion 메서드

    @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            log.info("postHandle [{}]", modelAndView);
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            String requestURI = request.getRequestURI();
            String logId = (String) request.getAttribute(LOG_ID);
            log.info("RESPONSE [{}][{}][{}]", logId, requestURI, handler);
            if (ex != null) {
                log.error("afterCompletion error!!", ex);
            }
        }
    }

    postHanlde은 컨트롤러 요청 뒤에 호출되는 것을 확인하기위해 로그만 출력했고, afterCompletion에서는 에러가 나도 에러를 확인할 수 있어서 로그로 찍어오는 코드까지 추가했다.

     

     

     

    위에는 스프링 인터셉터의 동작원리를 보기위한 로그를 찍는 코드이고 실제로 체크 해주는 코드는 다음과 같다.

    LoginCheckInterceptor 클래스

    @Slf4j
    public class LoginCheckInterceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
            String requestURI = request.getRequestURI();
    
            log.info("인증 체크 인터셉터 실행 {}", requestURI);
    
            HttpSession session = request.getSession();
    
            if (session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null) {
                log.info("미인증 사용자 요청");
                // 로그인으로 redirect
                response.sendRedirect("/login?redirectURL=" + requestURI);
                return false; // 더 이상 진행 안함.
            }
    
            return true;
        }
    }

     

     

     

     

    스프링 인터셉터도 WebConfig에서 스프링 빈으로 관리해준다.

    @Configuration
    public class WebConfig implements WebMvcConfigurer {
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            // 스프링이 제공하는 메서드를 인터셉터 해야함
            registry.addInterceptor(new LogInterceptor())
                    .order(1)
                    .addPathPatterns("/**")
                    .excludePathPatterns("/css/**", "/*.ico", "/error");
    
            registry.addInterceptor(new LoginCheckInterceptor())
                    .order(2)
                    .addPathPatterns("/**")
                    .excludePathPatterns("/", "/members/add", "login", "/logout",
                            "/css/**", "/*.ico", "/error");
        }
    }

    오버라이딩을 해야하는데 스프링에서 제공하는 메서드를 인터셉터 해야해서 그냥 메서드와 사용 방식에 익숙해져야한다.

    서블릿 필터와 다르게 모든 경로에서 사용하는 방법이 /*가 아닌 /**이다.

     

     

     

     

    여기도 서블릿 필터와 같은 LoginController를 사용한다.

    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 패턴' 카테고리의 다른 글

    API 예외처리 - HandlerExceptionResolver  (0) 2023.03.05
    스프링 부트의 오류 페이지  (0) 2023.03.03
    서블릿 필터  (0) 2023.03.01
    서블릿 Http 세션  (0) 2023.02.28
    로그인 페이지 만들기  (0) 2023.02.28
Designed by Tistory.