스프링 인터셉터

업데이트:

스프링 인터셉터

서블릿 필터와 마찬가지로 공통 관심 사항을 효과적으로 해결 할 수 있는 기술이다.

스프링 인터셉터 흐름

HTTP 요청 -> WAS -> 필터 -> 서블릿(디스패처 서블릿) -> 스프링 인터셉터 -> 컨트롤러 // 로그인 사용자
HTTP 요청 -> WAS -> 필터 -> 서블릿(디스패처 서블릿) -> 스프링 인터셉터 -> 컨트롤러(적절하지 않은 요청이라 판단, 컨트롤러 호출X) // 비 로그인 사용자

스프링 인터셉터 체인

HTTP 요청 -> WAS -> 필터 -> 서블릿(디스패처 서블릿) -> 인터셉터1 -> 인터셉터2 -> 컨트롤러

인터셉터는 서블릿 필터보더 더 다양하고 편리한 기능을 제공한다.

스프링 인터셉터 - 요청 로그

스프링의 인터셉터를 사용하려면 HandlerInterceptor 인터페이스를 구현하면 된다.

  • preHandle : 응답값이 true면 진행 false상태인 경우 진행이 되지 않는다.
  • postHandle : 컨트롤러가 호출 후에 호출된다.
  • afterCompletion : 뷰가 랜더링 된 이후에 호출된다.
  • 컨트롤러에서 예외가 발생하는 경우 postHandle은 호출되지 않고 afterCompletion은 항상 호출된다.
  • 서블릿 필터의 경우 지역변수로 해결이 가능하지만, 스프링 인터셉터는 호출 시점이 완전히 분리되어 있어 싱글톤 처럼 사용되어 멤버 변수로 사용하면 위험하다.
@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);

        //@RequsetMapping : handlerMethod
        //정적리소스 : ResourceHTTPRequestHandler
        if(handler instanceof HandlerMethod) {
            HandlerMethod hm = (HandlerMethod) handler;// 호출할 컨트롤러 메서드의 모든 정보가 포함되어 있다.
        }

        log.info("REQUEST [{}][{}][{}]", uuid, requestURI, handler);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandler [{}]", 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.info("afterCompletion err!!", ex);
        }
    }
}

등록

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor())
                .order(1)
                .addPathPatterns("/**") // 경로
                .excludePathPatterns("/css/**","/*.ico","/error"); //인터셉터가 먹지 않는 경로
    }
}

스프링 인터셉터 - 인증체크

  • 필터와 비교해서 코드가 매우 간결해지고 컨트롤러 호출되기 전에만 호출하면 된다. 따라서 preHandler만 구현하면 된다.
  • 적용하지 않거나 하지 않을 부분은 addPathPatterns, excludePathPatterns에 작성하면된다.
@Slf4j
public class LoginCheckInterceptor 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();
        log.info("인증 체크 인터셉터 실행 {}", requestURI);

        HttpSession session = request.getSession();

        if(session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null) {
            log.info("미인증 사용자 요청");
            response.sendRedirect("/login?redirectURL="+requestURI);
            return false;
        }
        String uuid = UUID.randomUUID().toString();

        request.setAttribute(LOG_ID, uuid);

        //@RequsetMapping : handlerMethod
        //정적리소스 : ResourceHTTPRequestHandler
        if(handler instanceof HandlerMethod) {
            HandlerMethod hm = (HandlerMethod) handler;// 호출할 컨트롤러 메서드의 모든 정보가 포함되어 있다.
        }

        log.info("REQUEST [{}][{}][{}]", uuid, requestURI, handler);
        return true;
    }
}

등록

@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");
    }

ArgumentResolver

arugemnt에 대해서 개발자가 직접 처리하기 위해 생성함

  • @Target(ElementType.PARAMETER) : 파라미터에만 사용
  • @Retention(RetentionPolicy.RUNTIME) : 리플렉션 등을 활용할 수 있도록 런타이까지 애노테이션 정보가 남아있음
@Slf4j
public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        log.info("supportsParameter 실행");

        //Login 어노테이션 사용 여부
        boolean hasParameterAnnotation = parameter.hasParameterAnnotation(Login.class);
        //Member 파라미터 사용 여부
        boolean hasMemberType = Member.class.isAssignableFrom(parameter.getParameterType());

        // 위에 조건이 해당하는 경우 resolveArgument로 넘어감
        return hasParameterAnnotation && hasMemberType;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

       log.info("resolveArgument 실행");

       HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
        HttpSession session = request.getSession(false);
        if(session == null){
            return null;
        }

        return session.getAttribute(SessionConst.LOGIN_MEMBER);

    }
}

사용

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Login {

}

@GetMapping("/")
public String homeLoginV3SpringArgumentResolver(@Login Member member, Model model) {



    //세션에 회원 데이터가 없으면
    if(member == null) {
        return "home";
    }

    //세션이 유지되면면
    model.addAttribute("member", member);
    return "loginHome";
}

등록

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new LoginMemberArgumentResolver());
    }

댓글남기기