BackEnd/패캠

Intercepter

제이드Jade 2022. 1. 24. 00:00
  •  filter와 유사하게 컨트롤러에 들어오는 요청 HttpRequest와 컨트롤러가 응답하는 HttpResponse를 가로채는 역할

 filter와 차이점

  •  이녀석은 Spring Context에 등록 된다는 것이다. => 어노테이션 여부, requestParam 등을 확인해서 차별을 둘 수 있음.
  • Filter는 DispatcherServlet이 실행되기 전 , Interceptor는 DispatcherServlet이 실행된 후
  •  Filter는 설정만 하면 되지만  Interceptor는 설정은 물론 메서드 구현이 필요

- AOP와 유사한 기능을 제공할 수 있으며 주로 인증 단계나 logging할 때 사용

 

/public/~ 으로 리퀘스트하면 무조건 통과,
/private/~에 있는 클래스들 중 어노테이션(여기선 우리가 만든 @Auth)이 붙어 있으면 검사를 진행 그 중 특정 리퀘스트 파라미터(여기선 name=steve)에서만 통과시키고 그 외에는 예외를 던지도록 하는 로직을 짜보자.

 

- controller

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

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

 

  •  private
@Auth
@Slf4j
@RequestMapping("/api/private")
@RestController
public class PrivateController {

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

 

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

 

 

어노테이션에서 사용하는 어노테이션들(=메타 어노테이션)

출처 : https://ncucu.me/146

@Target

@Target  Java compiler annotation 어디에 적용될지 결정하기 위해 사용

ElementType.PACKAGE : 패키지 선언

ElementType.TYPE : 타입 선언(클래스, 인터페이스, enum)

ElementType.ANNOTATION_TYPE : 어노테이션 타입 선언

ElementType.CONSTRUCTOR : 생성자 선언

ElementType.FIELD : 멤버 변수 선언(필드(인스턴스 변수) enum 상수)

ElementType.LOCAL_VARIABLE : 지역 변수 선언

ElementType.METHOD : 메서드 선언

ElementType.PARAMETER : 전달인자 선언

ElementType.TYPE_PARAMETER : 전달인자 타입 선언

ElementType.TYPE_USE : 타입 선언

   

@Retention

@Retetion  Annotation 실제로 적용되고 유지되는 범위

RetentionPolicy.RUNTIME

RetentionPolicy.CLASS

RetentionPolicy.SOURCE

   

RetentionPolicy.RUNTIME  컴파일 전까지만 유효. , 컴파일 이후에는 사라짐

RetentionPolicy.CLASS  컴파일러가 클래스를 참조할 때가지 유효.

RetentionPolicy.SOURCE  컴파일 이후에도 JVM 의해서 계속 참조가 가능. 주로 리플렉션이나 로깅에 많이 사용됨.

 

@Documented 

 => Java doc 문서화 여부를 결정

 

- AuthInterceptor

@Slf4j
@Component
public class AuthInterceptor implements HandlerInterceptor {
    // controller로 보내기 전에 처리하는 인터셉터
    // 반환이 false라면 남은 인터셉터를 날리고 controller로 요청을 안함
    // 매개변수 Object는 핸들러 정보를 의미한다.


    @Override //preHandle() 메서드는  컨트롤러가 호출되기 전에 실행
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //여기서 handler: 컨트롤러의 메서드를 참조할 수 있는 HandlerMethod 객체

        String url=request.getRequestURI();
        log.info("request url = {}",url);

        //특정 request-parameter에만 true를 반환하도록 해보자!!
        URI uri= UriComponentsBuilder.fromUriString(request.getRequestURI())
                .query(request.getQueryString())        //쿼리 => 주소뒤에 나오는 파라미터들
                .build()
                .toUri();

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

            String query=uri.getQuery();
            log.info(query);

            if (query.equals("name=jade"))
                return true;

            throw new AuthException();  //name=jade 파라미터가 아니면 내가 만든 예외를 던진다. //내가만든 클래스 import 해야함에 주의
        }
        return true;
    }

//    검사여부 대상인지 확인방법1 : 어노테이션 검사
    private boolean checkAnnotation(Object handler, Class clazz) //여기선 매개변수가 각각 메서드(or클래스)와 어노테이션
    {
        //resource가 javascript나 html이면 어노테이션이 안 달려 있어도 true
        if(handler instanceof ResourceHttpRequestHandler){
            return true;
        }

        //해당 anotation이 달려있는지 확인(달려있으면 true)
        HandlerMethod handlerMethod=(HandlerMethod) handler;

        if(null!=handlerMethod.getMethodAnnotation(clazz) || null!=handlerMethod.getBeanType().getAnnotation(clazz)){
            //메서드 || 클래스에 어노테이션이 달려있다면
            return true;
        }
        return false;
    }
}

 

  • Handler? 

클라이언트의 요청을 실제로 처리하는 것은 컨트롤러이고 DispatcherServlet 클라이언트의 요청을 전달받는 창구 역할을 한다. 앞서 설명 했듯이 DispatcherServlet 클라이언트의 요청을 처리할 컨트롤러를 찾기 위해 HandlerMapping 사용함.

스프링 MVC 요청을 실제로 처리하는 객체 핸들러(Handler)라고 표현하고 있으며, @Controller 적용 객체나 Controller 인터페이스를 구현한 객체 모두 스프링 MVC입장에서는 핸들러가 된다.

특정 요청 경로를 처리해주는 핸들러를 찾아주는 객체를 HandlerMapping이라고 부른다.

 

출처 : https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=sks6624&logNo=220794528484

 

 

- MvcConfig

@Configuration  //아마.. interceptor가 @component로 bean등록이 되어서 이게 나온거 일듯..
@RequiredArgsConstructor    //final인 변수에 대해서 생성자를 만들어 줌
public class MvcConfig implements WebMvcConfigurer {    //interceptor를 등록시키는 클래스

    private final AuthInterceptor authInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor).addPathPatterns("/api/private/*"); //검사 여부 확인 방법 2번째 : 해당 uri패턴 등록

        //addPathPatterns는 말그대로 인터셉터가 동작할 url 패턴
        //저 이후에도 계속 다른 interceptor를 등록할 수 있고, 차례대로 적용된다. 그렇게 여러가지 보안 방법을 적용시킬 수 있다.
    }
}

 

- AuthException

public class AuthException extends RuntimeException{    //커스텀예외 (구현은 exceptionAdvice에서)
}

- GlobalExceptionHandler

@RestControllerAdvice
public class GlobalExceptionHandler {

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