안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

이번 포스팅은 SpringBoot의 Interceptor에 대한 내용과 활용법에 대해 정리하고자 합니다.

먼저, 이번 포스팅에 많은 도움을 준 블로그 주소는 아래 링크입니다. Spring뿐만 아니라, 다양한 정보를 자세하게 설명해 주셨는데, 정말 도움 많이 되는 블로그여서 추천드립니다.!

(링크: https://mangkyu.tistory.com/173)

 

 

1. 인터셉터(Interceptor)란?

https://velog.io/@_koiil/Filter-Interceptor-AOP

 

a. 인터셉터 의미

 

인터셉터는 스프링에서 제공하는 기술로써 디스패처 서블릿(Dispacher Servlet)이 컨트롤러를 호출하기 전과 후에 요청과 응답을 참조하거나 가공할 수 있는 기능을 제공합니다. 스프링 레벨에서 지원하는 서블릿 필터이기 때문에, 스프링의 모든 빈 객체에 접근할 수 있으며, 활용도는 Filter와 달리, 부적인 인증/인가 단계나 로깅 작업 등 처리에 사용이 됩니다.

 

b. 인터셉터 메서드

preHandler() 컨트롤러가 호출되기 전에 사용됩니다. 따라서, 컨트롤러 이전에 처리해야하는 전처리 작업이나
요청 정보를 가공하는데 사용됩니다.
리턴 타입은 boolean으로, 반환값이 true이면 다음 단계로 진행되고, false면 요청이 중단되어
다음 인터셉터나 컨트롤러 절차가 진행되지 않습니다.

postHanler() 컨트롤러 메소드 실행 직후, view 페이지 렌더링 되기 전 사용합니다.
afterCompletion() 모든 뷰에서 최종 결과를 생성하는 일을 포함해 모든 작업이 완료되면 실행됩니다. 
postHandler와 달리 작업 중간에 예외가 발생하더라도 반드시 호출됩니다.

 

c. 인터셉터는 왜 사용할까?

 

인터셉터는 필터와 달리, 세부적인 요청을 처리하거나 가공하는데 용이합니다. Filter가 보다 앞선에서 공통적인 인증/인가 처리 등을 담당하는 역할을 수행한다면, 디스패처 서블릿을 통과한 인터셉터는 컨트롤러 이전에 필요한 요청 파라미터에 해당 객체가 가지는 내부적인 값에 값을 가공하거나, 특정 사용자의 세부적인 권한을 판단하여 요청 uri로의 접근 액세스 승인 혹은 거부하는 역할을 수행합니다.

 

 

2. 인터셉터 활용하기

/**
 * Group의 관리자를 위한 Interceptor
 *
 */
@Slf4j
@RequiredArgsConstructor
public class GroupAdminInterceptor implements HandlerInterceptor {

    private final static String MEMBER = "member";
    private final static String CREATOR = "creator";
    
    @Resource
    private GroupService groupService;

    @Resource
    private MemberService memberService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        // 1번
        Map<?, ?> pathVariables = (Map<?, ?>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);


        String email = request.getUserPrincipal().getName();
        Long uriId = Long.valueOf(String.valueOf(pathVariables.get("groupId")));

        try {
            Member member = memberService.findByEmail(email);
            Member creator = groupService.findById(uriId).getCreator();

            if (!Objects.equals(member.getId(), creator.getId())) {

                // 2번
                response.sendRedirect("/api/v1/groups/admin/errors?status=403");
                return false;
            }

        } catch (IllegalArgumentException e) {
            e.getMessage();
            response.sendRedirect("/api/v1/groups/admin/errors?status=404");
            return false;
        }
        return true;
    }

    // 3번
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }
}

주석 1번:

Map<?, ?> pathVariables = (Map<?, ?>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
인터셉터에서, 요청을 처리할 때, URL 정보를 가져오면, url에 있는 정보가 PathVariable인지 알 수가 없습니다. 이러한 문제점을 해결하기 위해, request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); 를 사용하면, url에서 pathvariable 변수명으로 key, value 형태로 처리할 수 있습니다. 따라서, Pathvariable에 해당하는 groupId(key)를 get(value)으로 가져올 수 있습니다.

 

 

주석 2번:

인터셉터는 boolean 값으로 다음 절차를 진행하는지 중단하는지 처리할 수 있습니다. 즉, 컨트롤러가 로직을 수행하기 전에 해당 인터셉터가 실행되는 것입니다. 이 코드에 한정해서 설명하면, group을 만든 creator와 지금 해당 uri로 접근을 요청한 member의 Id 정보가 같지 않다면, 다른 uri로 리다이렉트 요청을 수행하는 것입니다. 따라서, 위에서 설명드린 세부적인 권한을 판단하거나 인증/인가를 처리하는 데 사용될 수 있는 이유가 이러한 예시로 확인할 수 있습니다.

 

 

3. Config 및 예외 컨트롤러 등록하기

@Configuration
public class GroupAdminConfig implements WebMvcConfigurer {


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(groupAdminInterceptor())
                .order(1)
                .addPathPatterns("/api/v1/groups/admin/**")
                .excludePathPatterns("/static/**", "/css/**", "/*.icon", "/error", "/js/**",
                        "/api/v1/groups/admin/errors",
                        "/api/v1/groups/admin/list",
                        "/api/v1/groups/admin/new");
    }

    @Bean
    public GroupAdminInterceptor groupAdminInterceptor() {
        return new GroupAdminInterceptor();
    }
}

 

@GetMapping("/admin/errors")
public String getError(@RequestParam("status") String status) {

    log.info("status = {}", status);

    return "errors/404";

}

 

해당 인터셉터를 InterceptorRegistry에 등록하기 위해서, WebMvcConfigurer를 구현한 구현체를 생성합니다.

WebMvcConfigurer를 구현하면, void addInterceptors(InterceptorRegistry registry)를 구현하게 됩니다.  order()는 해당 인터셉터의 순서를 정하는 것이고, addPathPatterns는 해당 인터셉터의 적용을 처리할 Patterns를 정의합니다. 위의 예시의 경우, /api/1/groups/admin/ 을 포함하는 요청은 모두 인터셉터를 거치는 과정을 수행한다고  명시하는 것입니다. excludePathPatterns는 보통 static에 있는 정적 데이터를 excludePathPatterns 처리합니다.

 

 

4. 적용 확인하기

 

ex) A 계정의 사용자가 7번 그룹의 관리자일 때,

 

 

ex) B 계정의 사용자가 7번 그룹의 관리자가 아닐 때,

 

 

 

이상으로 SpringBoot 인터셉터(Interceptor)에 대한 글을 마치도록 하겠습니다. 감사합니다.!

+ Recent posts