본문 바로가기
Spring

스프링 AOP를 이용한 권한체크 작업해보기

by seeker00 2025. 3. 19.

스프링 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};
}

참고 페이지