개발공부/Spring Boot

[Spring Boot] Interceptor

환타몬 2022. 3. 1. 18:42

Interceptor

 

Interceptor란 Filter와 매우 유사한 형태로 존재하지만, 차이점은 Spring Cotext에 등록된다.

AOP와 유사한 기능을 제공할 수 있으며,

주로 인증 단계를 처리하거나 Logging를 하는 데에 사용한다.

 

이를 선/후처리 함으로써, Service business logic과 분리시킨다.

 

 

- Auth.java

package com.example.interceptor.annotation;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Auth {
}

 

- MvcConfig.java

package com.example.interceptor.config;

import com.example.interceptor.interceptor.AuthInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@RequiredArgsConstructor // final 로 선언된 객체들을 생성자에 주입받을 수 있도록 해줌
public class MvcConfig implements WebMvcConfigurer {

    //@Autowired 를 붙여주는 방법도 있지만 순환참조가 일어날 수 있음.
    private final AuthInterceptor authInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor).addPathPatterns("/api/private/*");
    }
}

 

- PrivateController.java

package com.example.interceptor.controller;

import com.example.interceptor.annotation.Auth;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/private")
@Auth //interceptor에서 이 다음 메소드인 컨트롤러에 Auth Annotation이 붙어있을 때만 통과시키기
@Slf4j
public class PrivateController {

    @GetMapping("/hello")
    public String hello(){
        log.info("private hello controller");
        return "private hello";
    }
}

 

 

- PublicController.java

package com.example.interceptor.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/public")
public class PublicController {

    @GetMapping("/hello")
    public String hello(){
        return "public hello";
    }
}

 

- AuthException.java

package com.example.interceptor.exception;

public class AuthException extends RuntimeException {

}

 

 

-GlobalExceptionHandler.java

package com.example.interceptor.handler;

import com.example.interceptor.exception.AuthException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(AuthException.class)
    public ResponseEntity authException(){
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
    }
}

 

- AuthInterceptor

package com.example.interceptor.interceptor;

import com.example.interceptor.annotation.Auth;
import com.example.interceptor.exception.AuthException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URI;

@Slf4j
@Component
public class AuthInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String url = request.getRequestURI();
        URI uri = UriComponentsBuilder.fromUriString(request.getRequestURI())
                .query(request.getQueryString()).build().toUri();

        log.info("request url :{}",url);
        boolean hasAnnotation = checkAnnotation(handler, Auth.class);
        log.info("has annotation {}", hasAnnotation);

        // 나의 서버는 모두 public으로 동작하는데
        // 단, Auth 즉 권한을 가진 요청에 대해서는 세션, 쿠키 등을 체크
        if(hasAnnotation){
            //권한체크
            String query = uri.getQuery();
            if(query.equals("name=steve")){
                return true;
            }

            throw new AuthException();
        }

        return true;
    }

    private boolean checkAnnotation(Object handler, Class clazz){

        //resource javascript, html,
        if( handler instanceof ResourceHttpRequestHandler){
            return true;
        }

        //annotation check
        HandlerMethod handlerMethod = (HandlerMethod) handler;

        //클래스의 핸들러메서드가 not null이거나
        //핸들러메서드의 getBeanType에 어노테이션이 달려있나 확인
        if(null != handlerMethod.getMethodAnnotation(clazz) ||
        null !=handlerMethod.getBeanType().getAnnotation(clazz)){
            // Auth annotation 이 있을 때는 true
            return true;
        }
        return false;
    }
}