contexa-iam

@Protectable Annotation

AI-driven method-level authorization that intercepts method invocations, evaluates SpEL-based security rules, and enforces rapid reentry protection with optional synchronous AI decision support.

Overview

The @Protectable annotation marks methods for AI-based authorization enforcement. Defined in contexa-common and processed by contexa-iam, it integrates with the XACML PEP layer to intercept method calls, evaluate security policies through SpEL expressions, and optionally invoke synchronous AI analysis for high-risk operations.

The processing pipeline consists of three key components working in sequence:

  1. ProtectableRapidReentryGuard —Prevents duplicate requests within a configurable time window
  2. ProtectableMethodAuthorizationManager —Evaluates SpEL-based security expressions against the current authentication context
  3. AuthorizationManagerMethodInterceptor —Orchestrates the full authorization flow as a Spring Security AuthorizationAdvisor

When to Use @Protectable

ApproachScopePolicy SourceAI IntegrationBest For
@Protectable Method-level Database (dynamic) Full (sync, #ai.* expressions) Business-critical operations, ownership-aware permission checks, AI-monitored endpoints
@PreAuthorize Method-level Annotations (static) None Simple role/permission checks that rarely change
Dynamic URL Auth URL-level Database (dynamic) Full (#ai.* expressions) URL-pattern-based access control, API endpoint protection

Quick Start

Basic Usage

Java
@Service
public class UserService {

    @Protectable
    public User getUser(Long userId) {
        // Protected by method-level policies from the database.
        // Policies are created through the IAM admin surfaces, primarily Policy Center and related policy authoring flows.
        return userRepository.findById(userId).orElseThrow();
    }
}

With Owner Verification

Java
@Service
public class OrderService {

    @Protectable(ownerField = "userId")
    public Order updateOrder(Order order) {
        // Enables owner-aware permission checks when the method policy uses
        // hasPermission(...) against the protected Order resource.
        return orderRepository.save(order);
    }
}

With Synchronous AI Analysis

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);
    }
}
Performance note: sync = true blocks the request until the synchronous Zero Trust decision path finishes. Use it only for operations that truly need pre-execution enforcement.

Annotation Definition

The annotation is declared in io.contexa.contexacommon.annotation and targets methods at runtime.

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

Attributes

AttributeTypeDefaultDescription
ownerField String "" Field or getter name used by ownership-aware hasPermission(...) evaluation. The value is resolved from the authorization target object (or a resolved entity for ID-based checks). When empty, ownership-aware permission checks are not applied.
sync boolean false When true, the Zero Trust evaluation completes synchronously before the method returns —the user's request blocks until LLM analysis finishes, so choose this option carefully. When false (default), evaluation runs asynchronously and the method proceeds immediately.

AuthorizationManagerMethodInterceptor

The core AOP interceptor that implements MethodInterceptor and AuthorizationAdvisor. It is registered at AuthorizationInterceptorsOrder.FIRST + 1 priority, ensuring it runs before standard Spring Security method interceptors.

Authorization Flow

Text
Method Invocation
Incoming call to @Protectable method
[1] RapidReentryGuard.check()
Denied —RapidProtectableReentryDeniedException (no event published)
[2] ProtectableMethodAuthorizationManager.protectable()
SpEL expression evaluated from #protectableRule variable. Denied —AuthorizationDeniedException
[3] @Protectable(sync=true)?
Yes —SynchronousProtectableDecisionService.analyze()
ALLOW —proceed | BLOCK / CHALLENGE / ESCALATE —ZeroTrustAccessDeniedException
[4] mi.proceed()
Method executes and returns result
[5] Publish Event + Record Metrics
Authorization event via ZeroTrustEventPublisher, duration via AuthorizationMetrics

Constructor

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

Optional Dependencies (setter injection)

SetterTypePurpose
setZeroTrustEventPublisher ZeroTrustEventPublisher Publishes authorization events for audit logging and AI-driven threat analysis. When absent, event publishing is silently skipped.
setMetricsCollector AuthorizationMetrics Records invocation duration in nanoseconds and authorization decision counts for monitoring and alerting. When absent, metrics collection is silently skipped.
setSynchronousProtectableDecisionService SynchronousProtectableDecisionService Required for sync=true. If absent, sync requests throw ZeroTrustAccessDeniedException.

Annotation Resolution

The interceptor resolves @Protectable annotations using a multi-level lookup strategy:

  1. Direct method annotation via AnnotationUtils.findAnnotation(method)
  2. Most specific method on the target class (handles proxy scenarios via AopProxyUtils)
  3. Class-level annotation on the target class
  4. Class-level annotation on the declaring class

ProtectableMethodAuthorizationManager

Evaluates SpEL-based security expressions against the current method invocation context. It relies on Spring Security's MethodSecurityExpressionHandler to create an evaluation context, then looks up the #protectableRule variable.

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
    }
}

The #protectableRule variable is an SpEL Expression injected into the evaluation context by the XACML policy engine. If the variable is missing or not of type Expression, access is denied.

ProtectableRapidReentryGuard

Prevents the same authenticated user from rapidly re-entering the same protected method within the configured guard window. The default window is 5 seconds, but the guard can be constructed with a different Duration. This protects high-risk operations from accidental duplicate submissions and burst retries.

Guard Logic

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[optional-arg0-hash]|HTTP_METHOD /uri"
    // 5. Attempt tryAcquire(userId, contextBindingHash, resourceKey, configured window)
    // 6. If not acquired, throw RapidProtectableReentryDeniedException
}

The resource key combines the method signature with the HTTP request details, ensuring that different URLs invoking the same method are tracked independently. The context binding hash incorporates session fingerprint data (User-Agent, IP, etc.) to prevent cross-session interference.

Repository Interface

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

Implementations

ImplementationModeStorageKey Format
InMemoryProtectableRapidReentryRepository Standalone ConcurrentHashMap<String, Instant> userId:contextHash:resourceKey
RedisProtectableRapidReentryRepository Distributed Redis SET NX EX security:protectable:rapid-reentry:{md5}

The in-memory implementation uses lock-free CAS operations with ConcurrentHashMap.putIfAbsent for thread safety. The Redis implementation uses setIfAbsent with TTL for distributed lock semantics, falling back to allow on Redis errors to prevent service disruption.

Connecting Policies to @Protectable Methods

@Protectable methods are enforced by policies stored in the database. The Resource Scanner discovers all @Protectable methods and synchronizes them as METHOD resources. In the current OSS UI, those resources are managed primarily through Policy Center. The underlying resource-management endpoints under /admin/workbench/resources/* are still used behind that integrated screen.

Method Identifier Format

Each @Protectable method is identified by its fully qualified signature:

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

Creating a Policy for a Method

Two approaches to create policies for @Protectable methods:

  1. Policy Center (recommended) —Open the Policy Center create surface, locate the scanned METHOD resource in the integrated resources area, define a permission, and continue into the same creation flow. This is the primary OSS screen that combines resource review, permission linkage, quick policy creation, AI-assisted setup, and policy listing.
  2. Policy service / direct creation —Create a policy with a METHOD target matching the method identifier and a valid SpEL condition (for example hasAuthority('ROLE_ADMIN') and #ai.isAllowed()). This is the underlying model used by Policy Center.

Behavior Matrix

ownerFieldsyncBehavior
"" (default)falseSpEL policy evaluation only, async AI analysis
""trueSpEL policy + synchronous AI blocking
"userId"falseSpEL policy + ownership-aware hasPermission(...) checks, async AI
"userId"trueSpEL policy + ownership-aware hasPermission(...) checks + synchronous AI blocking

Additional Usage Examples

Basic Protection

Protects a method with default settings: asynchronous evaluation, no owner-based filtering.

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

Owner-Scoped with Synchronous AI Analysis

Combines ownership-aware permission checks with synchronous Zero Trust analysis before the operation proceeds.

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 Outcomes (sync mode)

ActionBehavior
ALLOWMethod proceeds normally and the result is returned to the caller
BLOCKThrows ZeroTrustAccessDeniedException.blocked(resourceId) before the method executes
CHALLENGEThrows ZeroTrustAccessDeniedException requesting step-up authentication from the caller
ESCALATEThrows ZeroTrustAccessDeniedException.pendingReview(resourceId) before the method executes
PENDING_ANALYSISThrows ZeroTrustAccessDeniedException if the synchronous AI analysis service is unavailable or timed out

Event Publishing and Metrics

Authorization events are published via ZeroTrustEventPublisher.publishMethodAuthorization() after each invocation. Events are suppressed in the following cases:

  • RapidProtectableReentryDeniedException —reentry denials are not published
  • ZeroTrustAccessDeniedException —synchronous AI decisions handle their own event lifecycle
  • Synchronous protectable methods that return ALLOW —handled by the synchronous pipeline

When AuthorizationMetrics is available, the interceptor records:

  • recordProtectable(duration) —invocation duration in nanoseconds
  • recordAuthzDecision() —authorization decision count

ownerField Deep Dive

The ownerField attribute does not trigger an automatic post-return ownership scan. In the current OSS implementation it is injected into the method-security evaluation context and is used when the active method policy calls ownership-aware hasPermission(...) checks.

How ownerField Is Applied

When ownerField is configured, the method-expression handler passes it into CustomMethodSecurityExpressionRoot. Ownership is then enforced during policy expression evaluation, not after the method returns. The flow is:

  1. CustomMethodSecurityExpressionHandler resolves the @Protectable method and extracts ownerField.
  2. The handler injects #protectableRule and #ownerField into the evaluation context.
  3. If the policy expression calls hasPermission(target, permission) or hasPermission(targetId, targetType, permission), the root first delegates to the configured permission evaluator.
  4. If the permission evaluator grants access and ownerField is configured, the root resolves a direct field or matching getter from the authorization target object and compares it with Authentication.getName().

Comparison with Authenticated User

The resolved owner value is compared with Authentication.getName(). Users with ROLE_ADMIN bypass the ownership check. For non-admin users, a missing owner value or a mismatch fails closed and hasPermission(...) returns false.

When ownerField Does Not Exist

Important: The implementation first searches for a direct field and then for a matching getter such as getCreatedBy(). If neither exists, the ownership-aware permission check fails closed and access is denied for non-admin users.

Nested Field Access

The current OSS implementation does not walk nested dot-notation paths such as creator.id. Use a direct field or getter on the authorization target object instead.

Java
@Protectable(ownerField = "createdBy")
public Document getDocument(Long documentId) {
    // Ownership-aware checks resolve a direct field or getter such as getCreatedBy().
    return documentRepository.findById(documentId).orElseThrow();
}

If your domain model stores ownership in a nested object, expose a direct getter or dedicated field that returns the comparable owner identifier.

sync Mode Guidelines

When to Use Synchronous Mode

The sync attribute controls whether the AI Zero Trust assessment completes before or after the method returns. Choosing the right mode depends on the sensitivity of the operation and the acceptable latency.

ModeBehaviorLatency ImpactSecurity Guarantee
sync = true Blocks until the AI assessment completes. If the AI returns BLOCK, CHALLENGE, or ESCALATE, the method never executes. Higher latency (depends on LLM response time, typically 200ms—s) Strong —the operation is prevented before any side effects occur
sync = false (default) Uses cached or asynchronous AI assessment. The method proceeds immediately; AI analysis happens in the background. Minimal latency impact Eventual consistency —threats are detected after the fact and handled through event-driven remediation

Recommended Use Cases

Use sync = true for operations where preventing unauthorized actions is more critical than response time:

  • Financial transactions —Payment processing, fund transfers, refund operations
  • Data deletion —Permanent deletion of records, account closure, data purge operations
  • Privilege escalation —Role assignment, permission grants, admin-level operations
  • Data export —Bulk data downloads, report generation with sensitive data

Use sync = false (default) for read-heavy operations, listing endpoints, and actions where eventual consistency is acceptable and low latency is prioritized.

Managing @Protectable Methods via Admin

@Protectable methods are automatically discovered by the Resource Scanner and synchronized as METHOD resources. In the current OSS UI, the primary administration surface is Policy Center. Its integrated resources area works with the same managed-resource data that the legacy /admin/workbench/resources/* endpoints expose.

Step 1: Review METHOD Resources in Policy Center

Navigate to Admin > ID & Access Management > Policy Center and open the Resources tab. Filter by resource type METHOD to review discovered @Protectable methods. Each entry shows the fully qualified method signature (for example com.example.service.OrderService.updateOrder(Order)).

Step 2: Define a Permission for the METHOD Resource

From the integrated resources area, use Create Permission & Policy to define a permission for the selected method resource. Policy Center persists the permission-to-resource link by calling the managed-resource endpoints behind the integrated UI.

Step 3: Create or Refine the Policy in Policy Center

After the permission is linked, continue in Policy Center to create the actual policy. The current OSS flow supports quick creation, manual creation, AI-assisted setup, list review, simulation, and matrix analysis from the same integrated screen.

Step 4: Match the Policy to METHOD Targets

Method policies must use a METHOD target that matches the discovered method identifier. The underlying policy model remains the same whether the policy is created from quick mode, the AI-assisted create flow, or the manual create tab.

Current OSS UI Notes

  • Policy Center is the primary current UI and combines resource review, permission definition, policy creation, policy list, simulation, and matrix analysis.
  • The backend still exposes /admin/workbench/resources/* endpoints, and Policy Center uses them for resource-definition actions.
  • Standalone Resource Workbench and Policy Builder controller routes still exist in source, but the current OSS template set centers the user workflow on Policy Center.