메타-어노테이션(Meta-Annotation)"이라는 용어는 어노테이션을 정의할 때 사용되는 어노테이션을 지칭합니다. 즉, 어노테이션을 위한 어노테이션인 것이죠. Java에서는 커스텀 어노테이션을 정의할 때, 그 어노테이션의 동작 방식이나 적용 대상, 유지 정책 등을 지정하기 위해 메타-어노테이션을 사용합니다.
주로 사용되는 메타-어노테이션은 다음과 같습니다:
@Retention
: 이 어노테이션은 어노테이션이 유지되는 기간을 지정합니다. 예를 들어, RetentionPolicy.SOURCE
, RetentionPolicy.CLASS
, RetentionPolicy.RUNTIME
등이 있습니다.@Target
: 이 어노테이션은 해당 어노테이션이 적용될 수 있는 대상을 지정합니다. 예를 들어, 메소드, 필드, 클래스 등이 있습니다.@Documented
: 이 어노테이션은 해당 어노테이션이 Javadoc에 포함될지 여부를 지정합니다.@Inherited
: 이 어노테이션은 어노테이션이 하위 클래스에 상속될지 여부를 지정합니다.이러한 메타-어노테이션을 사용하여 커스텀 어노테이션의 동작을 지정하는 방식을 "메타-어노테이션 방법"이라고 부릅니다.
컨트롤러에서 로그인 한 유저인지 확인하는 커스텀 어노테이션 만들기
@Retention(RetentionPolicy.RUNTIME)
: @Retention
어노테이션은 어노테이션 정보가 어느 시점까지 유지될 것인지를 지정합니다. **RetentionPolicy.RUNTIME
**을 지정함으로써, 런타임 중에도 이 어노테이션 정보를 읽을 수 있습니다. 만약 **RetentionPolicy.CLASS
**나 **RetentionPolicy.SOURCE
**를 지정한다면, 런타임 중에는 해당 어노테이션 정보를 읽을 수 없습니다.@Target({ElementType.METHOD, ElementType.TYPE})
: @Target
어노테이션은 해당 어노테이션이 적용될 수 있는 대상의 종류를 지정합니다. **ElementType.METHOD
**와 **ElementType.TYPE
**을 지정함으로써, 이 어노테이션은 메서드와 클래스 혹은 인터페이스에 적용할 수 있음을 나타냅니다.@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 {
}
@RestControllerAdvice
: 예외 처리나, 바인딩 설정, 모델 어트리뷰트 등을 모든 **@Controller
**에 전역으로 적용할 수 있게 합니다.@Authentication
어노테이션을 확인합니다.@Authentication
어노테이션이 붙어 있다면, HTTP 헤더에서 "authorization" 필드로 JWT 토큰을 가져와 검증합니다. 검증을 통과하면 해당 유저의 ID를 반환합니다.@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;
}
}