1. 커스텀 어노테이션 소개
AOP와 함께 사용할 @AuthCheck 어노테이션은 메서드 수준에서 권한을 정의한다. 이 어노테이션에 필요한 권한을 설정하고, AOP를 통해 권한 검증 로직을 실행한다.

- String[] value() : 메서드에 접근 가능한 권한을 배열로 정의한다.
배열로 정의한 이유 : OWNER OR CUSTOMER 처럼 각각 접근 가능한 메서드가 따로 있지만 OWNER AND CUSTOMER 처럼 공통으로 접근 가능한 페이지가 있기 때문이다.


2. 커스텀 어노테이션을 활용하여 AOP 권한 검증

- 클래스 선언부
- @Slf4j : 로깅 기능을 제공하는 Lombok 어노테이션, log 객체를 자동으로 생성
- @Component : Spring Bean으로 등록. Spring 컨텍스트에서 AOP를 사용할 수 있도록 설정.
- @RequiredArgsConstructor : final로 선언된 필드에 대해 생성자를 자동으로 생성한다. HttpServletRequest를 주입받기 위해 사용.
- @Aspect : 이 클래스가 AOP를 구현한다는 것을 Spring에 알림.

- 필드 선언
- HttpServletRequest : 현재 요청의 정보를 담고 있는 객체. AOP에서 요청의 헤더나 속성(role, Id 등)을 가져오기 위해 사용
- final : @RequiredArgsConstructor와 함께 사용하여 의존성을 주입 받는다.

- 메서드 선언
- @Around: AOP 어드바이스로, 특정 시점(메서드 실행 전, 후)에 동작한다. 여기서는 @AuthCheck 어노테이션이 선언된 메서드의 실행을 가로챔.
- "@annotation(team11.team11project.common.aspect.AuthCheck)" : @AuthCheck 어노테이션이 붙은 메서드만 이 AOP의 대상이 된다.
- ProceedingJoinPoint joinPoint : AOP에서 현재 실행 중인 메서드에 대한 정보를 제공한다. 원래 메서드를 실행하거나 실행을 중단할 수 있다.

- 요청의 권한 가져오기
- 현재 요청에서 role 속성을 가져온다.
- role은 JWT 필터에서 설정한 사용자 권한(ex: OWNER, CUSTOMER) 이다.
- 이 값을 기반으로 권한 검증을 진행

- 어노테이션 정보 가져오기
- joinPoint.getSignature() : 현재 실행 중인 메서드의 정보를 가져온다.
- (MethodSignature) : 메서드의 시그니처를 가져와 타입 캐스팅한다.
- getMethod() : 실제 메서드 객체를 가져온다.
- getAnnotation(AuthCheck.class) : 메서드 선언된 @AuthCheck 어노테이션을 가져온다.
- 어노테이션에 설정된 권한 정보를 authCheck.value()로 확인할 수 있다.

- 요청에서 role 정보를 찾지 못한 경우 UserRoleNotFoundException을 발생시킨다.
- 이는 사용자 권한이 설정되지 않은 잘못된 요청임을 나타낸다.

- authCheck.value()
- @AuthCheck 애노테이션에 정의된 필요한 권한 목록이다.
- ex: {"OWNER", "CUSTOMER"}
- Arrays.stream(authCheck.value())
- 권한 목록을 스트림으로 변환
- noneMatch(role::equals)
- 현재 사용자 권한이 권한 목록(authCheck.value())에 포함되지 않은 경우 true를 반환한다.

- 권한 검증이 통과된 경우, 원래 메서드를 실행한다.
- 반환값은 원래 메서드의 결과이다.
2-1 JoinPoint 부분 AOP에서 현재 실행 중인 메서드에 대한 정보를 추출하는 과정을 더 자세히 설명
1) joinPoint.getSignature()
- ProceedingJoinPoint는 AOP에서 현재 실행 중인 메서드에 대한 모든 정보를 담고 있다.
- getSignature()는 현재 메서드의 시그니처(이름, 반환 타입, 파라미터 등)를 가져온다!
@Around("@annotation(AuthCheck)")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
// 현재 실행 중인 메서드의 시그니처 가져오기
System.out.println("Signature: " + joinPoint.getSignature());
return joinPoint.proceed();
}
// 출력 값
Signature: void com.example.MyService.myMethod(java.lang.String)
2)(MethodSignature)
- getSignature()의 반환값은 Signature 타입으로, 메서드의 기본 정보를 제공한다.
- MethodSignature는 Signature를 확장한 인터페이스로 메서드와 관련된 추가 정보를 제공한다.
- MethodSignature로 타입 캐스팅하면 메서드의 반환 타입, 이름, 파라미터 타입 등을 더 구체적으로 가져올 수 있다.
@Around("@annotation(AuthCheck)")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
// 메서드 이름 가져오기
System.out.println("Method Name: " + methodSignature.getMethod().getName());
// 반환 타입 가져오기
System.out.println("Return Type: " + methodSignature.getReturnType());
return joinPoint.proceed();
}
//출력
Method Name: myMethod
Return Type: void
3) getMethod()
- MethodSignature의 getMethod()는 현재 실행 중인 메서드 객체(java.lang.reflect.Method)를 반환한다.
- 이 메서드 객체를 사용하면 리플렉션을 통해 메서드에 선언된 어노테이션 등 추가 정보를 가져올 수 있다.
@Around("@annotation(AuthCheck)")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod(); // 메서드 객체 가져오기
System.out.println("Method: " + method);
return joinPoint.proceed();
}
// 출력
Method: public void com.example.MyService.myMethod(java.lang.String)
4) getAnnotation(AuthCheck.class)
- Method 객체에서 getAnnotation()을 사용하면 메서드에 선언된 특정 어노테이션을 가져올 수 있다.
- 여기서는 @AuthCheck 어노테이션을 가져오며, 어노테이션에 정의된 값(필요한 권한)을 확인한다.
@AuthCheck({"OWNER", "CUSTOMER"})
public void myMethod() {}
"메서드에 위와 같은 어노테이션이 선언되어 있다면"
@Around("@annotation(AuthCheck)")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
Method method = ((MethodSignature)joinPoint.getSignature)).getMethod();
// @AuthCheck 애노테이션 가져오기
AuthCheck authCheck = method.getAnnotation(AuthCheck.class);
if (authCheck != null) {
System.out.println("Required Roles: " + Arrays.toString(authCheck.value()));
}
return joinPoint.proceed();
}
//출력
Required Roles: [OWNER, CUSTOMER]
5) 동작 흐름 요약
- joinPoint.getSignature()
- 현재 실행 중인 메서드의 기본 정보를 가져온다.
- 반환값: Signature 타입
- (MethodSignature)
- Signature를 MethodSignature로 캐스팅하여 메서드와 관련된 추가 정보를 사용한다.
- ex : 반환 타입, 파라미터 타입 등
- getMethod()
- 현재 메서드의 Method 객체를 가져온다.
- 이 객체를 사용하면 리플렉션으로 어노테이션 정보를 확인할 수 있다.
- getAnnotation(AuthCheck.class)
- 현재 메서드에 선언된 @AuthCheck 어노테이션을 가져온다.
- 어노테이션에 설정된 권한 정보(value())를 확인할 수 있다.
3. 동작 흐름
- 클라이언트 요청
- 클라이언트가 API를 호출한다. 예를 들어 /api/store/create 요청(OWNER만 접근 가능)
- AOP가 메서드 실행을 가로챔
- AOP는 @AuthCheck가 선언된 메서드를 가로채서 실행한다.
- 권한 검증
- HttpServletRequest에서 사용자 권한을 가져온다.
- 어노테이션에 설정된 권한(AuthCheck.value())과 요청 권한을 비교한다.
- 권한 확인 후 처리
- 권한이 유효하면 원래 메서드를 실행(joinPoint.proceed())
- 권한이 유효하지 않으면 예외를 발생
'Back-End > Spring-MVC' 카테고리의 다른 글
| JWT 기반 인증/인가 흐름에서 AOP와 필터(URI기반) (0) | 2025.01.08 |
|---|---|
| JWT 기반 인증 및 역할별 API 설계와 구현 : 관리자와 사용자 권한 처리 (0) | 2025.01.06 |
| JWT(Json Web Token) (0) | 2025.01.04 |
| 스프링 글로벌 API 예외 처리와 @Valid 검증 예외 처리 (1) | 2024.12.31 |
| Servlet Filter (0) | 2024.12.12 |