스프링 인터셉터
업데이트:
스프링 인터셉터
서블릿 필터와 마찬가지로 공통 관심 사항을 효과적으로 해결 할 수 있는 기술이다.
스프링 인터셉터 흐름
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());
}
댓글남기기