Intercepter
- 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();
}
}