메타-어노테이션 명명 이유:

메타-어노테이션(Meta-Annotation)"이라는 용어는 어노테이션을 정의할 때 사용되는 어노테이션을 지칭합니다. 즉, 어노테이션을 위한 어노테이션인 것이죠. Java에서는 커스텀 어노테이션을 정의할 때, 그 어노테이션의 동작 방식이나 적용 대상, 유지 정책 등을 지정하기 위해 메타-어노테이션을 사용합니다.

주로 사용되는 메타-어노테이션은 다음과 같습니다:

이러한 메타-어노테이션을 사용하여 커스텀 어노테이션의 동작을 지정하는 방식을 "메타-어노테이션 방법"이라고 부릅니다.

상황 가정 :

컨트롤러에서 로그인 한 유저인지 확인하는 커스텀 어노테이션 만들기

예제:

  1. Custom Annotation interface 생성
    1. @Retention(RetentionPolicy.RUNTIME): @Retention 어노테이션은 어노테이션 정보가 어느 시점까지 유지될 것인지를 지정합니다. **RetentionPolicy.RUNTIME**을 지정함으로써, 런타임 중에도 이 어노테이션 정보를 읽을 수 있습니다. 만약 **RetentionPolicy.CLASS**나 **RetentionPolicy.SOURCE**를 지정한다면, 런타임 중에는 해당 어노테이션 정보를 읽을 수 없습니다.
    2. @Target({ElementType.METHOD, ElementType.TYPE}): @Target 어노테이션은 해당 어노테이션이 적용될 수 있는 대상의 종류를 지정합니다. **ElementType.METHOD**와 **ElementType.TYPE**을 지정함으로써, 이 어노테이션은 메서드와 클래스 혹은 인터페이스에 적용할 수 있음을 나타냅니다.
    3. 따라서, 아래의 코드에서 정의한 @Authentication 어노테이션은 런타임 시에도 정보를 유지할 수 있고, 메서드나 클래스/인터페이스에 적용할 수 있습니다. 이를 통해 런타임 중에 메서드나 클래스의 인증 상태를 체크하는 등의 처리를 할 수 있겠습니다.
package api.common.advice;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Authentication {
}
  1. 모든 컨트롤러 타기 전에 작동할 advisor 생성
    1. 메소드 내용
      1. @RestControllerAdvice: 예외 처리나, 바인딩 설정, 모델 어트리뷰트 등을 모든 **@Controller**에 전역으로 적용할 수 있게 합니다.
      2. 핸들러 메서드와 해당 메서드에 붙어있는 @Authentication 어노테이션을 확인합니다.
      3. 만약 @Authentication 어노테이션이 붙어 있다면, HTTP 헤더에서 "authorization" 필드로 JWT 토큰을 가져와 검증합니다. 검증을 통과하면 해당 유저의 ID를 반환합니다.
    2. @Authentication 어노테이션이 붙어 있는 메서드에 대해서만 JWT 검증을 수행합니다. 이렇게 하여 특정 API 요청에 대한 접근 제어나 사용자 인증을 더 세밀하게 관리할 수 있습니다.
package api.common.advice;

import api.utils.JwtProvider;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerMapping;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Objects;

@Slf4j
@RestControllerAdvice
@RequiredArgsConstructor
public class JwtAdvisor {

    private final JwtProvider jwtProvider;

    @ModelAttribute("userId")
    public String authorization() {
				// 현재 HTTP 요청 정보를 가져와 HttpServletRequest 객체에 저장합니다.
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

				// 현재 요청에 가장 잘 매칭되는 핸들러 메서드를 가져와 HandlerMethod 객체에 저장합니다.
        HandlerMethod handlerMethod = (HandlerMethod) RequestContextHolder.currentRequestAttributes()
                .getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);

				// 앞에서 가져온 HandlerMethod 객체에서 Java의 Method 객체를 추출합니다. 이 객체는 실행될 메서드에 대한 정보를 담고 있습니다.
        Method method = handlerMethod.getMethod();

				// 실행될 메서드에 @Authentication 어노테이션이 붙어 있는지 확인합니다. 있다면 auth 변수에 저장됩니다.
        Authentication auth = method.getAnnotation(Authentication.class);

				// @Authentication 어노테이션이 붙어 있다면, 이하의 로직을 수행합니다.
        if(Objects.nonNull(auth)){

						// 요청에서 "userId" 어트리뷰트를 가져와 userId 변수에 저장합니다.
            String userId = (String) request.getAttribute("userId");

						// 요청 헤더에서 "authorization" 값을 가져와 token 변수에 저장합니다.
            String token = request.getHeader("authorization");

            try{

                String validateResult = jwtProvider.validateToken(token);
                userId = validateResult;
            }catch (Exception ignored){}    // validation 예외 발생해도 무시

						// userId 값을 반환합니다. 이 값은 @ModelAttribute("userId")에 의해 모델 어트리뷰트로 설정됩니다.
            return userId;
        }

				// @Authentication 어노테이션이 없는 경우, null을 반환합니다.
        return null;
    }
}