스프링 AOP(Aspect Oriented Programming)
- AOP 는 관점 지향 프로그래밍을 말합니다.
- 간단하게 설명해보면 핵심적인 비즈니스 로직 외에 부가적인 공통 기능(흩어진 관심사)을 모듈화하는 것을 말합니다.
- 스프링 AOP는 프록시를 통해 구현되며, CGLIB 프록시(클래스 기반) 또는 JDK 동적 프록시(인터페이스 기반, 리플렉션)를 활용합니다.
주요 개념
Aspect
- 공통 관심 사항(흩어진 관심사)을 정의하는 모듈입니다.
- Advice 와 PointCut을 모듈화 한 것을 말합니다.
Advice
- 실질적인 부가 기능 로직을 당은 구현체를 말합니다.
- 스프링에서는 다섯 가지 종류의 Advice를 제공합니다.
- @Before : 메소드 실행 전에 동작을 수행합니다.
- @After : 메서드 실행 후에 동작을 수행합니다.
- @AfterReturning : 메서드가 성공적으로 반환된 후에 동작을 수행합니다.
- @AfterThrowing : 메서드에서 예외가 발생한 후에 동작을 수행합니다.
- @Around : 메서드 실행 전후에 동작을 수행하며, 메서드 실행을 직접 제어합니다.
Target
- Aspect를 적용하는 대상을 말합니다. 포인트 컷으로 결정 됩니다.
PointCut
- Advice를 수행할 지점을 지정, 필터링하는 기능입니다. 주로 AspectJ 표현식을 통해 정의할 수 있습니다.
JointPoint
- 프로그램 실행 중 특정 시점(메서드 호출, 객체 생성 등)을 말합니다. Advice가 적용될 수 있는 위치입니다.
- 스프링 AOP는 프록시 방식으로 동작하므로 조인 포인트는 항상 메소드 실행 지점으로 제한됩니다.
권한 체크 구현해보기
상황
- 현재 진행하는 프로젝트는 msa 아키텍쳐로 구현되어 있으며, 각 서비스는 시큐리티 의존성을 갖지 않는 상황입니다.
- 개발 작업을 진행하면서, presentation(controller) 레이어에서 시큐리티에서 제공하는 @PreAuthorize 와 유사하게 권한 체크 기능을 제공해 줄 필요성을 느끼게 되었습니다.
구현 방법 고민
- presentation(controller) 레이어에서 각 메서드(api) 수행 전에 권한 체크가 수행되도록 작업을 진행하고 싶었으므로, 인터셉터를 이용한 처리 방식과 aop를 이용한 처리와 같이 두 가지 방안을 고려해볼 수 있었습니다.
- 인터셉터는 기본적으로 컨트롤러 레이어 앞 단에서 동작하게 됩니다. 그렇기에 인터셉터를 활용하면 자연스럽게 처리가 가능할 것 같긴 했으나, 특정 어노테이션이 작성된 메서드(api)에서만 권한 체크를 수행하도록 처리하고 싶어 결국 AOP를 선택해 구현하게 되었습니다.
구현 예제
- AuthCheckAspect.java
@Aspect
@Component
@Slf4j(topic="AUTH CHECK::")
public class AuthCheckAspect {
@Before("@annotation(authCheck)")
public void authCheck(JoinPoint joinPoint, AuthCheck authCheck) throws Throwable {
Set<UserRole> roles = Set.of(authCheck.roles());
UserRole currentUserRole = getCurrentUserRole();
log.debug("유저 권한: {} - 필요 권한: {}", currentUserRole, roles);
if (!roles.contains(currentUserRole)) {
throw CustomException.from(ApiErrorCode.FORBIDDEN);
}
}
private UserRole getCurrentUserRole() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
throw CustomException.from(ApiErrorCode.UNAUTHORIZED);
}
HttpServletRequest request = attributes.getRequest();
String userRoleStr = request.getHeader("X-User-Role");
if (userRoleStr == null) {
throw CustomException.from(ApiErrorCode.UNAUTHORIZED);
}
return UserRole.valueOf(userRoleStr);
}
}
- AuthCheck.java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AuthCheck {
UserRole[] roles()
default {UserRole.ROLE_MASTER, UserRole.ROLE_HUB, UserRole.ROLE_DELIVERY, UserRole.ROLE_COMPANY};
}
참고 페이지
'Spring' 카테고리의 다른 글
QueryDsl의 Projections 활용 (0) | 2025.02.26 |
---|---|
스프링 로깅 Spring Logging (0) | 2025.02.24 |
[Jpa/QueryDsl] QueryDsl 설정 및 조회 기능 구현 (+동적 정렬) (0) | 2025.02.16 |
[Springboot]@WebMvcTest 사용 중 security csrf 오류 해결 (2) | 2023.11.26 |
Spring Security 기본 (0) | 2023.10.05 |