contexa-iam

@Protectable 어노테이션

메서드 호출을 가로채고, SpEL 기반 보안 규칙을 평가하며, 선택적 동기식 AI 결정 지원을 통한 빠른 재진입 보호를 적용하는 AI 기반 메서드 수준 인가입니다.

개요

@Protectable 어노테이션은 AI 기반 인가 적용 대상 메서드를 표시합니다. contexa-common에서 정의되고 contexa-iam에서 처리되며, XACML PEP 레이어와 통합하여 메서드 호출을 가로채고, SpEL 표현식을 통해 보안 정책을 평가하며, 고위험 작업에 대해 선택적으로 동기식 AI 분석을 호출합니다.

처리 파이프라인은 순서대로 작동하는 세 가지 핵심 구성 요소로 이루어져 있습니다:

  1. ProtectableRapidReentryGuard — 설정 가능한 시간 윈도우 내의 중복 요청을 방지
  2. ProtectableMethodAuthorizationManager — 현재 인증 컨텍스트에 대해 SpEL 기반 보안 표현식을 평가
  3. AuthorizationManagerMethodInterceptor — Spring Security AuthorizationAdvisor로서 전체 인가 흐름을 조정

@Protectable 사용 시점

접근 방식범위정책 소스AI 통합적합 용도
@Protectable 메서드 수준 데이터베이스 (동적) 전체 (sync, #ai.* 표현식) 비즈니스 핵심 작업, 소유자 검증 접근, AI 모니터링 엔드포인트
@PreAuthorize 메서드 수준 어노테이션 (정적) 없음 거의 변경되지 않는 단순 역할/권한 검사
동적 URL 인가 URL 수준 데이터베이스 (동적) 전체 (#ai.* 표현식) URL 패턴 기반 접근 제어, API 엔드포인트 보호

빠른 시작

기본 사용법

Java
@Service
public class UserService {

    @Protectable
    public User getUser(Long userId) {
        // Protected by method-level policies from the database.
        // Policies are authored through the current OSS Policy Center flow or compatible policy service paths.
        return userRepository.findById(userId).orElseThrow();
    }
}

소유자 검증 포함

Java
@Service
public class OrderService {

    @Protectable(ownerField = "userId")
    public Order updateOrder(Order order) {
        // Only the resource owner (order.userId == authenticated user)
        // or users with ROLE_ADMIN can invoke this method.
        return orderRepository.save(order);
    }
}

동기식 AI 분석 포함

Java
@Service
public class PaymentService {

    @Protectable(sync = true)
    public PaymentResult processPayment(PaymentRequest request) {
        // The request blocks until the AI Zero Trust analysis completes.
        // If the AI returns BLOCK, CHALLENGE, or ESCALATE,
        // ZeroTrustAccessDeniedException is thrown before execution.
        return paymentGateway.process(request);
    }
}
성능 참고: sync = true는 AI LLM 분석이 완료될 때까지 요청을 차단합니다. 실시간 AI 검증이 필수적인 고위험 작업(예: 결제, 데이터 내보내기)에만 사용하세요.

어노테이션 정의

이 어노테이션은 io.contexa.contexacommon.annotation에 선언되며 런타임에 메서드를 대상으로 합니다.

Java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Protectable {
    String ownerField() default "";
    boolean sync() default false;
}

속성

속성타입기본값설명
ownerField String "" 소유권 기반 인가 검사를 위해 리소스 소유자를 식별하는 데 사용되는 반환 타입의 필드명. 비어있으면 소유자 기반 필터링이 적용되지 않습니다.
sync boolean false true인 경우, 메서드가 반환하기 전에 Zero Trust 평가가 동기식으로 완료됩니다 — 사용자의 요청은 LLM 분석이 완료될 때까지 차단되므로 이 옵션을 신중하게 선택하세요. false(기본값)인 경우, 평가는 비동기적으로 실행되며 메서드는 즉시 진행됩니다.

AuthorizationManagerMethodInterceptor

MethodInterceptorAuthorizationAdvisor를 구현하는 핵심 AOP 인터셉터입니다. AuthorizationInterceptorsOrder.FIRST + 1 우선순위로 등록되어 표준 Spring Security 메서드 인터셉터보다 먼저 실행됩니다.

인가 흐름

Text
메서드 호출
@Protectable 메서드에 대한 수신 호출
[1] RapidReentryGuard.check()
거부 시 → RapidProtectableReentryDeniedException (이벤트 발행 안함)
[2] ProtectableMethodAuthorizationManager.protectable()
#protectableRule 변수에서 SpEL 표현식 평가. 거부 시 → AuthorizationDeniedException
[3] @Protectable(sync=true)?
예 → SynchronousProtectableDecisionService.analyze()
ALLOW → 진행 | BLOCK / CHALLENGE / ESCALATE → ZeroTrustAccessDeniedException
[4] mi.proceed()
메서드가 실행되고 결과를 반환
[5] 이벤트 발행 + 메트릭 기록
ZeroTrustEventPublisher를 통한 인가 이벤트, AuthorizationMetrics를 통한 소요 시간

생성자

Java
public AuthorizationManagerMethodInterceptor(
        Pointcut pointcut,
        ProtectableMethodAuthorizationManager authorizationManager,
        ProtectableRapidReentryGuard rapidReentryGuard)

선택적 의존성 (setter 주입)

Setter타입용도
setZeroTrustEventPublisher ZeroTrustEventPublisher 감사 로깅 및 AI 기반 위협 분석을 위한 인가 이벤트를 발행합니다. 없으면 이벤트 발행이 조용히 건너뛰어집니다.
setMetricsCollector AuthorizationMetrics 모니터링 및 알림을 위해 나노초 단위의 호출 소요 시간과 인가 결정 수를 기록합니다. 없으면 메트릭 수집이 조용히 건너뛰어집니다.
setSynchronousProtectableDecisionService SynchronousProtectableDecisionService sync=true에 필요합니다. 없으면 sync 요청이 ZeroTrustAccessDeniedException을 던집니다.

어노테이션 해석

인터셉터는 다단계 조회 전략을 사용하여 @Protectable 어노테이션을 해석합니다:

  1. AnnotationUtils.findAnnotation(method)를 통한 직접 메서드 어노테이션
  2. 대상 클래스에서 가장 구체적인 메서드 (AopProxyUtils를 통한 프록시 시나리오 처리)
  3. 대상 클래스의 클래스 수준 어노테이션
  4. 선언 클래스의 클래스 수준 어노테이션

ProtectableMethodAuthorizationManager

현재 메서드 호출 컨텍스트에 대해 SpEL 기반 보안 표현식을 평가합니다. Spring Security의 MethodSecurityExpressionHandler를 사용하여 평가 컨텍스트를 생성한 후 #protectableRule 변수를 조회합니다.

Java
public class ProtectableMethodAuthorizationManager {

    private final MethodSecurityExpressionHandler expressionHandler;

    public void protectable(
            Supplier<Authentication> authentication,
            MethodInvocation mi) {
        // Creates evaluation context with authentication and method invocation
        // Looks up #protectableRule variable (set by policy engine)
        // Evaluates expression; throws AuthorizationDeniedException on failure
    }
}

#protectableRule 변수는 XACML 정책 엔진에 의해 평가 컨텍스트에 주입되는 SpEL Expression입니다. 변수가 없거나 Expression 타입이 아닌 경우 접근이 거부됩니다.

ProtectableRapidReentryGuard

동일 인증 사용자가 5초 윈도우 내에 동일 보호 메서드를 호출하는 것을 방지합니다. 우발적인 중복 제출 및 보호된 엔드포인트에 대한 무차별 재시도 공격을 방어합니다.

가드 로직

Java
public void check(Authentication authentication, MethodInvocation methodInvocation) {
    // 1. Skip if not authenticated
    // 2. Resolve current HttpServletRequest from RequestContextHolder
    // 3. Generate context binding hash via SessionFingerprintUtil
    // 4. Build resource key: "ClassName.methodName|HTTP_METHOD /uri"
    // 5. Attempt tryAcquire(userId, contextBindingHash, resourceKey, 5s)
    // 6. If not acquired, throw RapidProtectableReentryDeniedException
}

리소스 키는 메서드 시그니처와 HTTP 요청 세부 정보를 결합하여 동일 메서드를 호출하는 서로 다른 URL이 독립적으로 추적되도록 합니다. 컨텍스트 바인딩 해시는 세션 간 간섭을 방지하기 위해 세션 핑거프린트 데이터(User-Agent, IP 등)를 포함합니다.

Repository 인터페이스

Java
public interface ProtectableRapidReentryRepository {
    boolean tryAcquire(String userId, String contextBindingHash,
                       String resourceKey, Duration window);
}

구현체

구현체모드저장소키 형식
InMemoryProtectableRapidReentryRepository 단독 ConcurrentHashMap<String, Instant> userId:contextHash:resourceKey
RedisProtectableRapidReentryRepository 분산 Redis SET NX EX security:protectable:rapid-reentry:{md5}

인메모리 구현은 스레드 안전성을 위해 ConcurrentHashMap.putIfAbsent를 사용한 잠금 없는 CAS 작업을 사용합니다. Redis 구현은 분산 잠금 시맨틱을 위해 TTL과 함께 setIfAbsent를 사용하며, 서비스 중단을 방지하기 위해 Redis 오류 시 허용으로 폴백합니다.

정책과 @Protectable 메서드 연결

@Protectable 메서드는 데이터베이스에 저장된 정책에 의해 적용됩니다. 리소스 스캐너가 모든 @Protectable 메서드를 자동으로 검색하고 ManagedResourceRepository와 현재 OSS Policy Center 흐름에 연결하여 권한 정의와 정책 작성에 사용할 수 있게 합니다.

메서드 식별자 형식

@Protectable 메서드는 정규화된 시그니처로 식별됩니다:

Text
com.example.service.OrderService.updateOrder(Order)

메서드에 대한 정책 생성

@Protectable 메서드에 대한 정책을 생성하는 두 가지 방법:

  1. Policy Center 생성 화면의 리소스 영역 (현재 OSS UI) — 관리자 콘솔의 Policy Center 생성 화면에서 스캔된 METHOD 리소스를 찾고, 통합된 리소스 영역에서 권한 정의 후 빠른 생성/수동 생성 흐름으로 정책 작성 단계에 진입합니다.
  2. 직접 정책 생성 — 메서드 식별자와 일치하는 METHOD 대상 유형의 정책을 생성하고 SpEL 조건 표현식을 설정합니다(예: hasRole('ADMIN') and #ai.isAllowed()).

동작 매트릭스

ownerFieldsync동작
"" (기본값)falseSpEL 정책 평가만, 비동기 AI 분석
""trueSpEL 정책 + 동기식 AI 차단
"userId"falseSpEL 정책 + 소유권 검증, 비동기 AI
"userId"trueSpEL 정책 + 소유권 + 동기식 AI 차단

추가 사용 예제

기본 보호

기본 설정으로 메서드를 보호합니다: 비동기 평가, 소유자 기반 필터링 없음.

Java
@Protectable
@PostMapping("/api/users/{id}/disable")
public void disableUser(@PathVariable Long id) {
    userService.disable(id);
}

소유자 범위 지정 + 동기식 AI 분석

업데이트를 리소스 소유자로 제한하고 작업을 허용하기 전에 실시간 AI 위험 평가를 활성화합니다.

Java
@Protectable(ownerField = "userId", sync = true)
@PutMapping("/api/accounts/{id}")
public Account updateAccount(@PathVariable Long id,
                             @RequestBody AccountDto dto) {
    return accountService.update(id, dto);
}

ZeroTrustAction 결과 (sync 모드)

액션동작
ALLOW메서드가 정상적으로 진행되고 결과가 호출자에게 반환됨
BLOCK예외 상세에 계산된 위험 점수가 포함된 ZeroTrustAccessDeniedException을 던짐
CHALLENGE호출자에게 단계별 인증을 요청하는 ZeroTrustAccessDeniedException을 던짐
ESCALATE관리자의 수동 검토 대기 중임을 나타내는 ZeroTrustAccessDeniedException을 던짐
PENDING_ANALYSIS동기식 AI 분석 서비스가 사용 불가하거나 타임아웃된 경우 ZeroTrustAccessDeniedException을 던짐

이벤트 발행 및 메트릭

인가 이벤트는 각 호출 후 ZeroTrustEventPublisher.publishMethodAuthorization()을 통해 발행됩니다. 다음과 같은 경우에는 이벤트가 억제됩니다:

  • RapidProtectableReentryDeniedException — 재진입 거부는 발행되지 않음
  • ZeroTrustAccessDeniedException — 동기식 AI 결정은 자체 이벤트 수명주기를 처리
  • ALLOW를 반환하는 동기식 protectable 메서드 — 동기식 파이프라인에서 처리

AuthorizationMetrics가 사용 가능한 경우 인터셉터는 다음을 기록합니다:

  • recordProtectable(duration) — 나노초 단위의 호출 소요 시간
  • recordAuthzDecision() — 인가 결정 수

ownerField 심화

ownerField 속성은 반환 객체의 필드를 현재 인증된 사용자의 ID와 비교하여 소유권 기반 인가를 활성화합니다. 이 검사는 메서드 실행 후에 실행되며, Java 리플렉션을 통해 반환 값을 검사합니다.

리플렉션이 필드 값을 추출하는 방법

ownerField가 지정되면 인터셉터는 리플렉션을 사용하여 메서드의 반환 객체에서 지정된 필드를 읽습니다. 프로세스는 다음 단계를 따릅니다:

  1. 메서드가 정상적으로 실행되어 반환 값을 생성합니다.
  2. 인터셉터가 반환 객체의 클래스(필요시 클래스 계층을 순회)에서 getDeclaredField(ownerField)를 호출합니다.
  3. 필드는 setAccessible(true)를 통해 접근 가능하게 되고 값이 추출됩니다.
  4. 추출된 값은 String으로 변환되어 인증된 사용자의 principal ID와 비교됩니다.

인증된 사용자와의 비교

추출된 소유자 필드 값은 Authentication.getName()에서 얻은 현재 사용자의 ID와 비교됩니다. 값이 일치하면 사용자는 리소스 소유자로 간주되어 접근이 허용됩니다(SpEL 정책도 통과한다는 전제 하에). ROLE_ADMIN을 가진 사용자는 소유권 검사를 완전히 우회합니다.

ownerField가 존재하지 않는 경우

중요: 지정된 ownerField가 반환 객체에 존재하지 않으면 리플렉션 조회가 조용히 실패하고 접근은 기본적으로 거부됩니다. 호출자에게 예외가 던져지지 않으며, 시스템은 이를 비소유자 접근 시도로 처리합니다. 필드명이 도메인 객체와 정확히 일치하는지 항상 확인하세요.

중첩 필드 접근

ownerField는 현재 OSS 구현 기준으로 직접 필드 또는 getter 이름을 지정할 때 사용합니다. 중첩 점 표기법(예: creator.id)은 지원하지 않으므로, 인가 대상 객체에서 직접 비교 가능한 소유자 필드/게터를 노출해야 합니다.

Java
@Protectable(ownerField = "ownerId")
public Document getDocument(Long documentId) {
    // Returns Document with a nested Creator object.
    // The interceptor resolves document.ownerId
    // and compares it to the authenticated user's ID.
    return documentRepository.findById(documentId).orElseThrow();
}

체인의 중간 객체가 null인 경우 접근 검사가 조용히 실패하고 접근이 거부됩니다.

sync 모드 가이드라인

동기식 모드 사용 시점

sync 속성은 AI Zero Trust 평가가 메서드 반환 전 또는 후에 완료되는지를 제어합니다. 올바른 모드 선택은 작업의 민감도와 허용 가능한 지연 시간에 따라 달라집니다.

모드동작지연 시간 영향보안 보장
sync = true AI 평가가 완료될 때까지 차단합니다. AI가 BLOCK, CHALLENGE 또는 ESCALATE를 반환하면 메서드는 실행되지 않습니다. 높은 지연 시간 (LLM 응답 시간에 따라 일반적으로 200ms-2s) 강력 — 부작용이 발생하기 전에 작업이 방지됨
sync = false (기본값) 캐시되거나 비동기 AI 평가를 사용합니다. 메서드는 즉시 진행되며 AI 분석은 백그라운드에서 수행됩니다. 최소한의 지연 시간 영향 최종적 일관성 — 위협은 사후에 감지되어 이벤트 기반 교정을 통해 처리됨

권장 사용 사례

비인가 작업을 방지하는 것이 응답 시간보다 더 중요한 작업에 sync = true를 사용하세요:

  • 금융 거래 — 결제 처리, 자금 이체, 환불 작업
  • 데이터 삭제 — 레코드 영구 삭제, 계정 폐쇄, 데이터 퍼지 작업
  • 권한 에스컬레이션 — 역할 할당, 권한 부여, 관리자 수준 작업
  • 데이터 내보내기 — 대량 데이터 다운로드, 민감한 데이터가 포함된 보고서 생성

읽기 위주 작업, 목록 엔드포인트, 최종적 일관성이 허용되고 낮은 지연 시간이 우선시되는 작업에는 sync = false(기본값)를 사용하세요.

Admin을 통한 @Protectable 메서드 관리

@Protectable 메서드는 리소스 스캐너에 의해 자동으로 검색되고 현재 OSS 기준의 Policy Center 리소스 흐름에서 METHOD 유형 리소스로 관리됩니다. 이 섹션에서는 현재 UI에서 이들을 찾고, 정의하고, 보호하는 방법을 설명합니다.

1단계: Policy Center에서 METHOD 리소스 찾기

관리자 콘솔에서 Policy Center를 연 뒤 생성 화면의 통합 리소스 영역에서 METHOD 유형을 확인합니다. 각 항목은 정규화된 메서드 시그니처(예: com.example.service.OrderService.updateOrder(Order))를 표시합니다.

2단계: 비즈니스 이름 정의

METHOD 리소스를 클릭하여 메타데이터를 편집합니다. 사람이 읽을 수 있는 비즈니스 이름(예: "고객 주문 업데이트")과 선택적 설명을 할당합니다. 이 비즈니스 이름은 정책 생성 및 감사 로그 검토 시 관리자 콘솔 전체에서 표시되어 비기술 관리자가 각 메서드의 기능을 쉽게 이해할 수 있게 합니다.

3단계: 권한 생성

리소스 상세 보기에서 "Create Permission"을 클릭하여 이 메서드에서 허용되는 작업을 정의합니다. 권한 이름(예: ORDER_UPDATE), 설명, 작업 유형(READ, WRITE, DELETE 등)을 지정합니다. 권한은 이 METHOD 리소스에 자동으로 연결됩니다.

4단계: 정책 할당

권한이 정의되면 해당 권한을 행사할 수 있는 사람을 제어하는 정책을 생성합니다. 두 가지 접근 방식이 있습니다:

Policy Wizard를 통한 빠른 부여

Policy Wizard는 일반적인 인가 패턴을 위한 가이드 흐름을 제공합니다. 권한을 선택하고 대상 역할 또는 사용자를 선택하며 선택적으로 시간 기반 또는 속성 기반 조건을 추가합니다. Wizard가 기본 SpEL 표현식을 자동으로 생성합니다.

Policy Center를 통한 고급 설정

Policy Center의 리소스/생성 흐름에서 정책 작성을 시작하면 선택한 METHOD 리소스를 기준으로 정책 조건과 대상을 조정할 수 있습니다. 이 흐름에서는:

  • 선택한 METHOD 리소스로 리소스 대상이 사전 입력되어 메서드 시그니처를 수동으로 입력할 필요가 없습니다.
  • 조건 편집기가 호환 가능한 조건을 필터링하여 메서드 수준 정책에 유효한 SpEL 표현식만 표시합니다(예: #ai.isAllowed(), hasRole(), hasPermission(), 소유자 기반 검사).
  • 메서드 수준 대상에 적용되지 않는 URL 전용 표현식(경로 매처 등)은 숨겨집니다.

이 from-resource 모드는 선택한 메서드에 실제로 적용 가능한 옵션만으로 범위를 제한하여 설정 오류를 크게 줄여줍니다.