AI Security Expressions
Extend Spring Security's SpEL expression system with Zero Trust action predicates. Contexa's AbstractAISecurityExpressionRoot exposes the current ZeroTrustAction to both URL-level and method-level authorization decisions.
Overview
Contexa extends Spring Security's expression-based access control with AI-integrated predicates that evaluate the current ZeroTrustAction. These expressions can be used in both URL-level security configurations and method-level annotations.
The expression system is built on two levels:
- URL-Level -
CustomWebSecurityExpressionRootfor HTTP request matching in URL policies - Method-Level -
CustomMethodSecurityExpressionRootfor annotation-based authorization with@Protectable
Both classes extend AbstractAISecurityExpressionRoot, which provides the shared #ai.* expression API. At runtime, the expression roots resolve the current ZeroTrustAction from request-scoped state and repository-backed lookups; method-level evaluation adds a short-lived Caffeine cache on top of that lookup path.
Expression API Reference
All expressions are accessed through the #ai prefix in SpEL. These are defined in AbstractAISecurityExpressionRoot and available at both URL and method levels.
| Expression | Returns | Description |
|---|---|---|
#ai.isAllowed() |
boolean |
Returns true if the AI assessment result is ALLOW |
#ai.isBlocked() |
boolean |
Returns true if the AI assessment result is BLOCK |
#ai.needsChallenge() |
boolean |
Returns true if the AI assessment result is CHALLENGE |
#ai.needsEscalation() |
boolean |
Returns true if the AI assessment result is ESCALATE |
#ai.isPendingAnalysis() |
boolean |
Returns true if the AI assessment result is PENDING_ANALYSIS |
#ai.hasAction(String) |
boolean |
Checks whether the current ZeroTrustAction matches a specific action string |
#ai.hasActionIn(String...) |
boolean |
Returns true if the current action matches any of the provided action strings |
#ai.hasActionOrDefault(defaultAction, actions...) |
boolean |
Uses defaultAction when no action is currently available, then checks whether the resolved action is included in the provided action list |
URL-Level Expressions
CustomWebSecurityExpressionRoot extends AbstractAISecurityExpressionRoot and adds URL-specific helper methods for HTTP request matching. It is used when persisted URL policies are evaluated through Spring Security's web expression layer.
Additional URL-Level Methods
| Method | Returns | Description |
|---|---|---|
hasIpAddress(String ipAddress) |
boolean |
Checks if the request originates from the given IP address or CIDR range |
getHttpMethod() |
String |
Returns the HTTP method of the current request (GET, POST, etc.) |
Usage Examples
These expressions are used in stored URL policy conditions managed through the IAM admin surfaces:
// Allow only if AI approves AND user has USER role
#ai.isAllowed() and hasRole('USER')
// Allow if AI approves AND request is from internal network
#ai.isAllowed() and hasIpAddress('10.0.0.0/8')
// Deny only if AI explicitly blocks (permissive mode)
!(#ai.isBlocked())
// Allow or challenge actions only
#ai.hasActionIn('ALLOW', 'CHALLENGE')
Method-Level Expressions
CustomMethodSecurityExpressionRoot extends AbstractAISecurityExpressionRoot and adds method-level authorization capabilities including ownership verification and full hasPermission() support.
Additional Method-Level Methods
| Method | Returns | Description |
|---|---|---|
hasPermission(Object, Object) |
boolean |
Full permission evaluation with ownership checking via CompositePermissionEvaluator |
hasPermission(Object, String, Object) |
boolean |
Target-type-based permission evaluation with domain-specific routing |
Action Lookup
Method-level expressions resolve ZeroTrustAction through a layered lookup path:
- Request attribute - if the current HTTP request already carries
contexa.zeroTrustAction, that action is used first - Caffeine local cache - 5-second TTL, maximum 10,000 entries, method-level only
ZeroTrustActionRepositorylookup - shared backing lookup used by both URL and method evaluation; the concrete storage depends on deployment configuration
Usage with @Protectable
Method-level AI expressions are typically used with the @Protectable annotation and @PreAuthorize:
@PreAuthorize("#ai.isAllowed() and hasPermission(#id, 'USER', 'READ')")
@Protectable(ownerField = "userId")
public User getUser(Long id) { ... }
@PreAuthorize("#ai.isAllowed() and hasPermission(#orderId, 'ORDER', 'UPDATE')")
@Protectable(ownerField = "customerId", sync = true)
public void updateOrder(Long orderId, OrderDto dto) { ... }
@PreAuthorize("!(#ai.isBlocked()) and hasRole('ADMIN')")
@Protectable
public List<AuditLog> getAuditLogs() { ... }
URL vs Method Comparison
The two expression roots serve different authorization layers and have distinct capabilities:
| Feature | URL-Level | Method-Level |
|---|---|---|
| Expression Root | CustomWebSecurityExpressionRoot |
CustomMethodSecurityExpressionRoot |
| Action Lookup | Request attribute + repository lookup | Request attribute + Caffeine (5s TTL) + repository lookup |
| Ownership Check | No | Yes (via ownerField) |
hasPermission() |
Stripped (URL-level only) | Full support |
hasIpAddress() |
Yes | No |
| Trigger | HTTP request matching | @Protectable annotation |
| Typical Use | Stored URL policies | Service method annotations |
ZeroTrustAction Flow
AI security expressions evaluate the ZeroTrustAction generated by the AI analysis pipeline. The following diagram shows how a request flows from initial analysis to authorization decision:
Action Types
| Action | Meaning | Typical Response |
|---|---|---|
ALLOW |
AI assessment indicates low risk | Grant access normally |
BLOCK |
AI assessment indicates high risk or threat | Deny access, log incident |
CHALLENGE |
AI assessment indicates medium risk | Require additional verification (MFA, CAPTCHA) |
ESCALATE |
AI assessment requires human review | Route to security team for manual approval |
PENDING_ANALYSIS |
AI analysis is still in progress | Apply default policy or wait for completion |
Action Resolution Model
The public AI expression API is action-based. It does not expose a numeric riskScore(...) function. Runtime code resolves the current request to a Zero Trust action such as ALLOW, BLOCK, CHALLENGE, ESCALATE, or PENDING_ANALYSIS, and #ai.* expressions read that action.
Resolved action = ALLOW | BLOCK | CHALLENGE | ESCALATE | PENDING_ANALYSIS
Examples:
#ai.isAllowed()
#ai.isBlocked()
#ai.needsChallenge()
#ai.hasActionIn('ALLOW', 'CHALLENGE')
#ai.hasActionOrDefault('BLOCK', 'ALLOW')
Practical Scenarios
Scenario 1: Risk-Based Access Control
Use AI expressions to implement tiered access control based on risk level:
- Low risk —AI returns
ALLOW, access is granted - Medium risk —AI returns
CHALLENGE, require additional authentication (MFA) - High risk —AI returns
BLOCK, access is denied
// Strict mode: only allow if AI explicitly approves
#ai.isAllowed()
// Permissive mode: allow unless AI explicitly blocks
!(#ai.isBlocked())
// Challenge-aware: allow if permitted or after challenge completion
#ai.hasActionIn('ALLOW', 'CHALLENGE')
Scenario 2: AI + Role-Based Hybrid
Combine AI risk assessment with traditional role-based access control for defense in depth:
// Require both AI approval and appropriate role
#ai.isAllowed() and hasAnyAuthority('ROLE_USER', 'ROLE_ADMIN')
// Admin bypass with AI monitoring (AI can still block suspicious admin activity)
hasRole('ADMIN') and !(#ai.isBlocked())
// Elevated access requires both AI approval and specific authority
#ai.isAllowed() and hasAuthority('PERMISSION_SENSITIVE_DATA_READ')
Scenario 3: Ownership with AI Verification
Combine AI expressions with @Protectable ownership checking and permission evaluation for fine-grained access control:
// AI + ownership + permission: triple-layered authorization
@Protectable(ownerField = "createdBy", sync = true)
@PreAuthorize("#ai.isAllowed() and hasPermission(#id, 'DOCUMENT', 'UPDATE')")
public void updateDocument(Long id, DocumentDto dto) { ... }
// AI + ownership for read operations
@Protectable(ownerField = "patientId")
@PreAuthorize("#ai.isAllowed() and hasPermission(#id, 'MEDICAL_RECORD', 'READ')")
public MedicalRecord getRecord(Long id) { ... }
// Fallback when AI is unavailable: default to deny
@Protectable(ownerField = "accountId")
@PreAuthorize("#ai.hasActionOrDefault('BLOCK', 'ALLOW') and hasPermission(#id, 'ACCOUNT', 'UPDATE')")
public void updateAccount(Long id, AccountDto dto) { ... }
Creating AI-Aware Policies in Admin
AI security expressions can be used in stored SpEL policies managed through the OSS IAM admin surfaces. In current OSS code, administrators work primarily through /admin/policy-center, selecting compatible conditions or entering #ai.* expressions directly.
Step 1: Open Policy Center
Navigate to /admin/policy-center and choose the appropriate creation flow for the target resource. For URL policies, define the target pattern and HTTP method. For method-oriented rules, start from the scanned resource list or related resource-admin entry points that redirect back into Policy Center.
Step 2: Select AI Condition Template
In the condition editor, use the supported #ai.* expressions directly or combine them with role, permission, time, and IP conditions. Typical action-based checks include:
- AI Approved —
#ai.isAllowed() - AI Not Blocked —
!(#ai.isBlocked()) - AI Approved + Role —
#ai.isAllowed() and hasRole('...') - Custom AI Expression —Write any valid SpEL using
#ai.*functions
Step 3: Confirm Activation Status
Policies can move through approval states. In OSS code, PENDING and REJECTED policies are skipped by CustomDynamicAuthorizationManager; active runtime policies are typically APPROVED or NOT_REQUIRED.
- NOT_REQUIRED —Loaded without an approval gate.
- PENDING —Visible for review but not enforced at runtime.
- APPROVED / REJECTED —Approved policies load into runtime mappings; rejected policies do not.
Step 4: Monitor Policy Effectiveness
After saving a policy, verify it through Policy Center lists, approval status, simulator, matrix analysis, and audit/security monitoring:
- Confirm whether the policy is
APPROVED,NOT_REQUIRED,PENDING, orREJECTED - Cross-check the resulting SpEL condition and target scope before rollout
- Use the Policy Center simulator and matrix views to inspect expected outcomes
- Review allow, deny, challenge, and escalation outcomes in audit and security monitoring flows
Cache Architecture
AI security expressions rely on cached ZeroTrustAction objects to avoid re-running AI analysis on every expression evaluation. The caching strategy differs between URL-level and method-level expressions.
Cache Layers
| Cache Layer | Scope | TTL | Max Entries | Used By |
|---|---|---|---|---|
| Request Attribute | Single HTTP request | Request lifetime | 1 per request | Both URL and Method level |
| Caffeine | JVM-local | 5 seconds | 10,000 | Method-level only |
| Repository-backed shared lookup | Shared runtime state | Repository implementation dependent | Implementation dependent | Both URL and Method level |
The request attribute cache ensures that within a single HTTP request, the ZeroTrustAction is resolved only once regardless of how many expressions reference it. The Caffeine cache at the method level provides low-latency access for repeated evaluations across requests within a short time window.