프로젝트[종료]

15. Spring Interceptor 적용하기

알렉스 페레이라 2023. 5. 4. 21:53

저번에는 사용자가 Servlet이 제공하는 Filter를 사용하여 웹을 관리했다.

지금부터는 Spring이 제공하는 Interceptor에 대해서 알아보고, 적용해보겠다.

 

Interceptor

  • 스프링 인터셉터도 서블릿 필터와 같이 웹과 관련된 공통 관심 사항을 효과적으로 해결할 수 있는 기술이다.
  • 서블릿 필터가 서블릿이 제공하는 기술이라면, 스프링 인터셉터는 스프링 MVC가 제공하는 기술이다.
  • 둘다 웹과 관련된 공통 관심 사항을 처리하지만, 적용되는 순서와 범위, 그리고 사용방법이 다르다.
public interface HandlerInterceptor {
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {}
    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 {}
}

Interceprot 동작 흐름

  • 스프링 인터셉터는 디스패처 서블릿과 컨트롤러 사이에서 컨트롤러 호출 직전에 호출 된다.
  • 스프링 인터셉터는 스프링 MVC가 제공하는 기능이기 때문에 결국 디스패처 서블릿 이후에 등장하게 된다. 스프링 MVC의 시작점이 디스패처 서블릿이라고 생각해보면 이해가 될 것이다.
  • 스프링 인터셉터에도 URL 패턴을 적용할 수 있는데, 서블릿 URL 패턴과는 다르고, 매우 정밀하게 설정할 수 있다.
  • 스프링 인터셉터는 체인으로 구성되는데, 중간에 인터셉터를 자유롭게 추가할 수 있다.
  • 서블릿 필터의 경우 단순하게 doFilter() 하나만 제공된다. 인터셉터는 컨트롤러 호출 전( preHandle ), 호출 후( postHandle ), 요청 완료 이후( afterCompletion )와 같이 단계적으로 잘 세분화 되어 있다.
  • 서블릿 필터의 경우 단순히 request , response 만 제공했지만, 인터셉터는 어떤 컨트롤러( handler )가 호출되는지 호출 정보도 받을 수 있다. 그리고 어떤 modelAndView 가 반환되는지 응답 정보도 받을 수 있다.

 

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

 

 

  • Interceptor의 UrlPattern
? 한 문자 일치
* 경로(/) 안에서 0개 이상의 문자 일치
** 경로 끝까지 0개 이상의 경로(/) 일치
{spring} 경로(/)와 일치하고 spring이라는 변수로 캡처
{spring:[a-z]+} matches the regexp [a-z]+ as a path variable named "spring"
{spring:[a-z]+} regexp [a-z]+ 와 일치하고, "spring" 경로 변수로 캡처
{*spring} 경로가 끝날 때 까지 0개 이상의 경로(/)와 일치하고 spring이라는 변수로 캡처
/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 variable

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/util/pattern/PathPattern.html

 

PathPattern (Spring Framework 6.0.8 API)

Compare this pattern with a supplied pattern: return -1,0,+1 if this pattern is more specific, the same or less specific than the supplied pattern.

docs.spring.io

 

 

1.SessionInterceptor.java를 생성한다.

경로 : (\src\main\java\com\project\web\session\SessionInterceptor.java)

package com.project.web.session;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.servlet.HandlerInterceptor;

public class SessionInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //요청이 들어온 URI
        String requestURI = request.getRequestURI();
        
        //사용자 정보가 저장된 Session
        HttpSession session = request.getSession();

        //세션이 없거나, 세션에 로그인정보가 없다면
        if (session == null || session.getAttribute(SessionConst.SESSION_NAME) == null) {
            response.sendRedirect("/sign/signIn?redirectURL=" + requestURI);
            return false; //return타입이 boolean이기때문에 false를 해줘야 다음 process로 넘어가지 않는다.
        }
        return true;
    }
}

 

2.@Configuration 클래스에서 WebMvcConfigurer를 implements하여, addInterceptors메소드를 Override한다.

경로(\src\main\java\com\project\config\WebConfig.java)

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SessionInterceptor())
                .order(0) //interceptor의 순서를 정한다.
                .addPathPatterns("/**") // [/**]는 전부 Interceptor의 범위에 있다는 뜻이다.
                .excludePathPatterns("/", "/sign/*", "/assets/*", "/images/*"); //다음과 같은 url은 interceptor에서 제외한다.
    }

 

3.서버 재시작후 테스트

로그인하지 않은 상태에서 localhost:9090/generic으로 접근한다.

로그인 페이지로 redirect됐으며, 접근 시도했던 /generic경로를 redirectURL파라미터로 던진다.

 

4.로그인

성공!

 

다음에는

  • ArgumentResolver를 사용하여 Session상태를 좀 더 쉽게 확인하는 기능.
  • 로그인 여부에 따라 홈 화면을 구분.
  • 로그아웃기능.

 을 만들 예정이다.