[Spring] 스프링 인터셉터

HSRyuuu dev blog·2023년 4월 14일
0

Spring Interceptor

스프링 인터셉터는 스프링 MVC가 제공하는 웹과 관련된 공통 관심사항을 효과적으로 해결할 수 있는 기술이다.
예를들어 로그인 되지 않은 사용자가 로그인해야지만 접근할 수 있는 페이지에 접근하려 할때,
세션을 확인하여 세션에 로그인된 사용자의 유무에 따라 접근 허용 여부를 설정해 줄 수 있다.

1) 스프링 인터셉터 흐름

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

  • 서블릿 필터는 서블릿이 호출되기 이전에 호출된다.
  • 스프링 인터셉터는 컨트롤러 호출 직전에 호출된다.
  • URL패턴을 서블릿 필터에 비해 정밀하게 설정할 수 있다.

2) 스프링 인터셉터 제한

  1. 접근 허용 시
    HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 -> 컨트롤러
  2. 접근 거절 시
    HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터(접근 거부, 컨트롤러 호출 x) -> 종료

3) 스프링 인터셉터 체인

스프링 인터셉터는 체인으로 구성되고, 순서를 자유롭게 변경할 수 있다.
HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 인터셉터1 -> 인터셉터2 ... -> 컨트롤러

interface HandlerInterceptor

  • 컨트롤러 호출 전(preHandle)

  • 컨트롤러 호출 후(postHandle)
    ->컨트롤러에서 예외가 발생하면 호출되지 않는다.

  • 요청 완료 이후(afterCompletion)
    ->항상 호출된다.
    ->예외 발생시 예외ex를 파라미터로 받아서 예외 정보를 로그로 출력할 수있다.

3개의 메소드중 원하는것만 구현해서 사용하면 된다.

public interface HandlerInterceptor {

	default boolean preHandle(HttpServletRequest request, 
    						  HttpServletResponse response,
    						  Object handler)throws Exception { return true;}

	default void postHandle(HttpServletRequest request,HttpServletResponse response, 
    						Object handler, 
                            @Nullable ModelAndView modelAndView) throws Exception { }

	default void afterCompletion(HttpServletRequest request,
    							  HttpServletResponse response, 
                                  Object handler,
                                  @Nullable Exception ex) throws Exception { }

Configuration

HandlerInterceptor를 implements 하여 메소드를 구현한 클래스를 만든 뒤,
WebConfig를 만들어서 addInterceptors 메소드를 구현해야한다.

해당 메소드 내에 registry.addInterceptor를 통해 인터셉터를 추가하면 된다.

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
       registry.addInterceptor(/*인터셉터 생성*/)
                .order(1) //첫번째로 실행 될 인터셉터로 등록
                .addPathPatterns(/*URL 경로*/) 
                .excludePathPatterns(/*제외할 URL 경로*/);
    }
}
  • .order(int n) : n번째로 실행 할 인터셉터로 등록
  • .addPathPatterns() : ( )하위에 전부 적용
  • .excludePathPatterns : .addPathPatterns에 등록되었지만, 그중 제외할 URL 지정

예제1) 로그 인터셉터

@Slf4j
public class LogInterceptor implements HandlerInterceptor {

    public static 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(); // uuid 생성
        request.setAttribute(LOG_ID,uuid); //afterCompletion에서 사용하기위해 request에 넣는다.

        //@RequestMapping을 사용하는 경우 : HandlerMethod가 사용된다.
        //정적 리소스를 사용하는 경우 : ResourceHttpRequestHandler 가 사용된다.
        if(handler instanceof HandlerMethod){
            HandlerMethod hm = (HandlerMethod) handler;
            //HandlerMethod의 여러가지 메소드를 사용하여 호출할 컨트롤러 메서드의 정보를 얻을 수 있다.
        }
        log.info("----------new REQUEST------------");
        log.info("REQUEST [{}][{}] | uuid : [{}]", requestURI, handler, uuid);

        //true 반환 시 다음 인터셉터 또는 컨트롤러가 호출된다.
        //false 반환 시 다음이 호출되지 않고 종료된다.
        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();
        //위의 preHandle에서 생성한 uuid를 request에 넣어뒀던것을 가져온다.
        String uuid = (String) request.getAttribute(LOG_ID); 

        log.info("RESPONSE [{}][{}] | uuid : [{}]", requestURI, handler, uuid);
        
        //오류발생시 오류 로그도 찍어주자.
        if(ex!=null){
            log.error("afterCompletion error!!", ex);
        }
    }
}

예제2) 로그인 체크 인터셉터

preHandle만 필요해서 필요한것만 구현했다.

@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();//request에서 session을 가져온다
        // 세션이 비어있거나, 세션에서 "loginId"가 비어있을때 접근 거부를 위한 로직
        if(session == null || session.getAttribute("loginId") == null){
            log.info("미인증 사용자 요청");
            // [redirectURL=(requestURI)&msg=true] 라는 쿼리를 달아서 로그인 화면으로 redirect한다.
            response.sendRedirect("/login?redirectURL="+requestURI+"&msg=true");
            //false 반환 시 다음 인터셉터가 호출되지 않는다.
            return false;
        }

        return true;
    }
}

예제1,2) 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("/","/member/add","/board","/board/post/*","/login",
                				"/logout","/css/**","/*.ico","/error");
    }
}
  • "/**" : / 하위의 모든 경로
  • "/board" : 정확히 '/board' 만 해당
  • /css/** : /css로 시작하는 모든 경로
  • /*.ico : .ico로 끝나는 경로 제외
  • /board/post/* : /board/post/(숫자) 인 경로

(참고)김영한님 인프런 Spring MVC-2
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2

profile
Exciting dev life / 댓글, 피드백, 질문 환영합니다 !!!

0개의 댓글