보통 로그인이 되어 있는지 처리를 하기 위해 HandlerInterceptor
의 구현체를 만들어 사용합니다. 그리고 이 인터셉터 구현체의 작동을 제한하기 위해서 등록 시 인터셉터가 작동할 path 패턴을 지정해줄 수 있습니다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(final InterceptorRegistry registry) {
registry.addInterceptor(new AuthInterceptor())
.addPathPatterns("/api/posts");
}
}
하지만 이렇게 할 경우 URL에 대해서는 인터셉터를 설정할 수 있지만 HTTP 메서드에 대해서는 설정을 할 수 없습니다. 이는 굉장히 불편합니다. 예를 들어 보겠습니다. /api/posts
라는 게시물에 대한 API URL이 존재한다고 하겠습니다. 이 URL에 대해 다음과 같은 2가지 메서드가 매핑되어 있습니다.
GET /api/posts
: 전체 게시물 조회(로그인이 필요하지 않음)POST /api/posts
: 게시물 작성(로그인이 필요함)하지만 인터셉터는 URL에 대해서만 설정할 수 있기 때문에 /api/posts
에 대해 인터셉터를 설정하면 GET
요청이 들어오든 POST
요청이 들어오든 인터셉터가 기능하도록 설정할 수 밖에 없습니다. 결국 다음과 같은 코드가 추가되어야 합니다.
public class AuthInterceptor implements HandlerInterceptor {
...
@Override
public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) throws Exception {
if (!HttpMethod.POST.matches(request.getMethod())) {
return true;
}
...
}
...
}
그런데 만약 같은 인터셉터를 사용해야 하는데 이번에는 GET
에서도 작동해야 한다면 어떻게 될까요? /api/posts
에서는 GET
메서드에 대해 인터셉터가 설정되면 안되므로 preHandle
안에 if (!HttpMethod.GET.matches(request.getMethod()))
를 사용할 수는 없습니다. 결국 GET
용 인터셉터, POST
용 인터셉터, 이런 식으로 여러 개의 인터셉터를 만들어줘야 합니다. 이런 불편함을 어노테이션을 통해 해결할 수 있다면 어떨까요? 예를 들어 컨트롤러의 @LoginRequired
이라는 어노테이션이 붙은 메서드에 대해서만 인터셉터가 동작하도록 할 수 있지 않을까요?
우선 동작의 대상이 될 어노테이션부터 만들어보도록 하겠습니다.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {
}
컨트롤러의 메서드에만 붙을 어노테이션이므로 @Traget(ElementType.METHOD)
로 선언해주도록 하겠습니다. 이제 인터셉터에서는 이 어노테이션이 붙은 메서드인지 체크해주면 됩니다. preHandle
의 매개변수로 들어오는 handler
를 활용해서 어노테이션을 체크해주도록 하겠습니다.
public class AuthInterceptor implements HandlerInterceptor {
...
@Override
public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler)
throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler; // (1)
LoginRequired loginRequired = handlerMethod.getMethodAnnotation(LoginRequired.class); // (2)
if (Objects.isNull(loginRequired)) {
return true; // (3)
}
... // (4) (인터셉터의 로직 처리)
}
...
}
HandlerMethod
의 기능을 사용하기 위해 Object
타입으로 들어온 handler를 HandlerMethod
타입으로 형변환해줍니다. 윗줄의 instanceof
과정은 비검사 형변환 과정에서 런타임 에러를 방지하기 위함입니다.HandlerMethod
의 getMethodAnnotation
메서드로 우리가 정의한 RequireLogin
어노테이션을 꺼냅니다.@LoginRequired
어노테이션이 붙어있는 메서드가 아니라면 getMethodAnnotation
으로 꺼낼 값이 없으므로 loginRequired
값은 null이 될 것입니다. 따라서 null인 경우 이 인터셉터가 처리할 메서드가 아니라고 판단하여 true를 반환해 컨트롤러의 동작을 진행시킵니다.@LoginRequired
이 붙어 있는, 즉 로그인이 필요한 메서드입니다. 로그인 검증 처리를 하는 로직을 작성하면 됩니다.그리고 컨트롤러에서는 로그인이 필요한 메서드에 @LoginRequired
어노테이션을 붙여주면 됩니다.
@RestController
public class PostsController {
...
@GetMapping("/api/posts")
public ResponseEntity<List<Post>> showPosts() {
...
}
@PostMapping("/api/posts")
@LoginRequired // `@LoginRequired`이 붙었으므로 로그인을 체크한다.
public ResponseEntity<Void> createPost(@RequestBody PostCreateRequest postCreateRequest) {
...
}
...
}
이렇게 원하는 메서드에 대해서만 인터셉터가 동작하도록 설정할 수 있습니다.
참고자료
우아한테크코스 4기 모라고라 팀
🙈[Spring] Interceptor (2) - 어노테이션 작성 및 접근 권한, 세션 처리🐵
찾던 내용인데 감사합니다!