Java - Spring &&n SpringBoot

Spring - 스프링 인터셉터(3) 스프링 인터셉터 개념과 로그 남기기

TerianP 2022. 8. 13.
728x90

스프링 인터셉터란?

  • 스프링 인터셉터는 사실상 서블릿 필터와 동일한 기능을 제공한다.
  • 다만 서블릿 필터가 서블릿이 제공하는 기술이라면, 스프링 인터셉터는 스프링MVC 가 제공하는 기술로 적용되는 순서와 범위, 사용방법이 다르다.
  • 스프링 인터셉터를 위해서는 HandlerInterceptor 인터페이스를 구현한다.
  • 또한!! 스프링에서 제공하는 만큼 서블릿 필터보다 더 강력하고 좋다

 

스프링 인터셉터의 동작

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

  • 스프링 인터셉터는 디스패쳐 서블릿과 컨트롤러 사이에서 컨트롤러 직전에 호출된다 ⇒ 따라서 서블릿 필터 뒤에 실행된다
  • 스프링 인터셉터는 결국 MVC 에서 제공하는 기능이기 때문에 결국 디스패쳐 서블릿 이후에 등작하게 된다. 이는 스프링 MVC 의 시작점이 디스패쳐 서블릿이라는 것을 생각해보면 오히려 이해가 된다.
  • 스프링 인터셉터에도 URL 패턴을 적용할 수 있는데 서블릿 uRL 패턴과는 다르고, 매우 정밀하게 설정 가능하다.

HTTP 요청 → WAS → 필터 → 서블릿 → 인터셉터1 → 인터셉터2 → 컨트롤러

  • 서블릿 필터와 마찬가지로 인터셉터도 여러개를 걸 수 있다

 

스프링 인터셉터의 흐름

정상 흐름

출처 : 김영한의 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술

  • PreHandle : 컨트롤러 호출전에 호출된다 → 핸들러 어댑터 호출전에 호출
    • 이때 preHandle 의 응답값이 true 면 다음으로 진행하고, false 면 더는 진행하지 않는다. false 인 경우 나머지 인터셉터는 물론이고, 핸들러 어댑터도 호출되지 않는다. 즉 그림의 1번에서 끝!!
  • PostHandle : 컨트롤러 호출 후에 호출된다. 더 정확히는 핸들러 어댑터 호출 후에 호출된다.
  • afterCompletion : 뷰가 랜더링 된 이후에 호출된다.

 

예외 상황 발생

출처 : 김영한의 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술

  • PreHandle : 컨트롤러 호출전에 호출된다.
  • postHandle : 컨트롤러에서 예외가 발생하는 경우 postHandle 은 호출되지 않는다.
  • afterCompletion : 얘는 예외가 발생하던 말던 상관없이 무조건 호출된다.

 

스프링 인터셉터로 로그 남기기

LogInterceptor

  • 서블릿 필터와 다른 점은 ServletRequest가 아닌 HttpServletRequest 가 바로 넘어온다는 점과 각 핸들러마다 handler 이라는 객체와 각 핸들러에 맞는 특징적인 객체 - modelAndView, Exception - 등이 함께 넘어온다는 점이다.
  • 여기서 handler 는 MVC 의 handlerAdepter 를 의미하는 것으로 쉽게 이야기해서 스프링 컨트롤러를 의미한다. 따라서 이 핸들러 안에는 인터셉터 뒤에 오는 컨트롤러 메서드에 대한 모든 정보가 담긴다 ⇒ 이 정보들을 꺼내서 사용 가능하다
  • postHandle 에는 ModelAndView 객체가 들어온다. 따라서 ModelAndView 를 꺼내서 사용 가능하다
package HJproject.Hellospring.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;

@Slf4j
public class LogInterceptor implements HandlerInterceptor {

    public static final StringLOG_ID= "logId";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String reqURI = request.getRequestURI();
        String uuid = UUID.randomUUID().toString();

        // preHandle 뒤에오는 인터셉터 핸들러에서 uuid 를 동일한 사용하기 위해서
        request.setAttribute(LOG_ID, uuid);

        // 컨트롤러에서 @RequestMapping 처럼 사용하는 경우 HandlerMethod 를 사용한다.
        // 이쪽은 Controller 의 Handler 과 HandlerAdepter 와 관련있다
        // 정적 리소스 : ResourceHttpRequestHandler

        if (handler instanceof HandlerMethod) {
            HandlerMethod hm = (HandlerMethod) handler; // 호출할 컨트롤러 메서드의 모든 정보가 포함된다
        }

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

    // postHandle 는 ModelAndView 를 매개변수로 받는다
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandler [{}]", modelAndView);
    }

    // afterCompletion 은 Exception 를 매개변수로 받는다
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        String reqURI = request.getRequestURI();
        String logId = (String)request.getAttribute(LOG_ID);

log.info("RESPONSE [{}] [{}] [{}]", logId, reqURI, handler);
        if (ex != null) {
log.error("aferCompletion Error!!!!", ex);
        }
    }
}

 

SpringConfig 에 등록하기

  • 등록은 서블릿 필터와는 다르게 Bean 등록이 아닌 WebMvcConfigurer 인터페이스를 오버라이딩해서 등록하는 방식이다.
@Configuration // 스프링 빈에 등록하기 위한 설정파일이라는 Annotation
public class SpringConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor())
                .order(1)
                .addPathPatterns("/**") // 패턴 적용 방식이 서블릿 패턴과는 상이하다
                .excludePathPatterns("/css/**", "/*.ico", "/error"); // 예외 패턴에 대해서 작성 가능!

    }
}

 

Spring 인터셉터 URL 패턴

- 스프링 인터셉터에서 제공하는 URL 패턴은 서블릿 필터에서 사용되던 URL 과는 완전히 다르다! 더욱 자세하고 세밀하게 설정 가능하다!

- 참고로 이 패턴은 정규식과도 은근 비슷하고, 특히나 스프링 시큐리티에서 사용되는 패턴과도 묘하게 일치한다...

패턴 적용 내용
? 한 문자 일치
* 경로 / 안에서 0 개 이사의 문자 일치
** 경로 끝까지 0개 이상의 경로 / 일치
{SPRING} 경로 / 와 일치하고 SPRING 이라는 변수로 캡처
{SPRING:[a-z]+} 정규표현식 regexp [a-z] 와 일치하고 "SPRING" 이라는 경로 변수로 캡처
{*SPRING} 경로가 끝날 때까지 0개 이상의 경로 / 와 일치하고 SPRING 이라는 변수로 캡처

 

PathPattern 공식문서에서 추가 내용을 찾아 볼 수 있다

/pages/t?st.html — matches /pages/test.html,

/pages/tXst.html but not /pages/ toast.html /resources/*.png — matches all .png files in the resources directory

/resources/** — matches all files underneath the /resources/ path, including / resources/image.png and /resources/css/spring.css

/resources/{*path} — matches all files underneath the /resources/ path and captures their relative path in a variable named "path"; /resources/image.png will match with "path" → "/image.png", and /resources/css/spring.css will match with "path" → "/css/spring.css"

/resources/{filename:\\w+}.dat will match /resources/spring.dat and assign the value "spring" to the filename variabl

로그찍기 코드 확인

- 로그는 서블릿 필터 뒤에 인터셉터의 로그가 남는다.

- 따라서 서블릿 필터 -> 스프링 인터셉터 라는 순서를 확인 가능하다.

- 로그인한 상태에서 찍히는 PostHandle 에서 ModelAndView 를 확인가능하다. 동시에 Handle 객체를 통해서 어떤 컨트롤러의 어떤 메서드를 사용하는지 매개변수가 무엇인지도 확인 가능하다!!

ModelAndView 에서 Model 에 뭐가 남아졌는지 어떤 view 로 넘어가는지 모두 확인가능하다!


스프링 인터셉터 로그인 체크

LoginCheckInterceptor

package HJproject.Hellospring.interceptor;

import HJproject.Hellospring.Session.SessionConst;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

// 스프링 인터셉터는 서블릿 필터에서 사용하던 화이트 리스트를 따로 작성할 필요가 없다!
// ==> 단, SpringConfig 에 추후 Interceptor 을 등록할때 특정 url 을 등록하면 된다.
@Slf4j
public class LoginCheckInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String reqURI = request.getRequestURI();

log.info("인증 체크 인터셉터 실행!! {}", reqURI);

        HttpSession session = request.getSession();
        if (session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null) {
log.info("미인증 사용자 요청");

            // 로그인으로 redirect
            response.sendRedirect("/login?redirectURL="+reqURI);
            return false;
        }

        return true;
    }
}

SpringConfig

@Configuration // 스프링 빈에 등록하기 위한 설정파일이라는 Annotation
public class SpringConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor())
                .order(1) // 인터셉터 순서
                .addPathPatterns("/**") // 패턴 적용 url
                .excludePathPatterns("/css/**", "/*.ico", "/error"); // 패턴 예외 url

        registry.addInterceptor(new LoginCheckInterceptor())
                .order(2) // 인터셉터 순서
                .addPathPatterns("/**") // 패턴을 적용하고자 하는 url
                // 패턴 예외 url
                .excludePathPatterns("/**/js/*.js", "/index", "/", "/home", "/members/newregisters", "/login", "/logout","/css/**", "/*.ico", "/error");

    }

    // Servlet Filter 를 사용하기 위한 Bean
    //@Bean
    public FilterRegistrationBean logFilter(){
        FilterRegistrationBean<Filter> filterBean = new FilterRegistrationBean<Filter>();
        filterBean.setFilter(new LogFilter());

        // 필터 순서 => 필터 체인 시 사용되는 순서
        filterBean.setOrder(1);

        // 필수 적용 URL => 필터 적용 시 사용되는 url : /* 라면 모든 url 에 적용됨
        filterBean.addUrlPatterns("/*");

        return filterBean;
    }
}

로그인 체크 코드 확인

- 로그인 없이 members/member_list 에 접근을 시도하자 인터셉터가 발동하여 접근을 막은 모습

함정카드 발동! 스프링 인터셉터!

- admin 으로 로그인 인터셉터에 걸리지 않고 접근 가능하다

로그인 완료 후 무난히 접근 가능하다

 

댓글