contexa-iam

Dynamic URL Authorization

Database-driven URL authorization using CustomDynamicAuthorizationManager. Define access policies through the current Policy Center and related admin policy flows, then have them enforced without rebuilding the application.

What is Dynamic Authorization?

Traditional Spring Security uses static URL matchers defined in Java code. Any change to authorization rules requires a code change, rebuild, and redeployment. Contexa's CustomDynamicAuthorizationManager takes a fundamentally different approach: authorization policies are loaded from the database at startup and evaluated at runtime.

This architecture enables three key capabilities:

  • No-redeploy policy changes —Authorization rules can be modified, added, or removed without rebuilding or redeploying the application. After editing, a hot-reload triggers re-initialization.
  • Admin policy management —Policies can be created and managed through Policy Center, policy detail flows, and related admin endpoints without changing Java security configuration.
  • AI-generated policy integration —AI-generated and AI-evolved policies can be integrated into the authorization pipeline after human approval.

How It Works

CustomDynamicAuthorizationManager implements Spring Security's AuthorizationManager<RequestAuthorizationContext> interface. It bridges the gap between database-stored policies and Spring Security's runtime evaluation engine.

Policy Evaluation Flow

Text
Initialization
Application Startup
Policy Center / Admin Policy Flows
PolicyRetrievalPoint (PRP)
Loads policies
Database (Policies)
Rules, Conditions, Targets
CustomDynamicAuthorizationManager
Builds RequestMatcherEntry list from policies + URL targets
Per-Request Evaluation
PolicyExpressionConverter
Converts each active URL policy into the SpEL string used by Spring Security
ExpressionAuthorizationManagerResolver
Builds the Spring AuthorizationManager used for each cached request matcher entry
SpEL Evaluation
hasAnyAuthority(), hasPermission(), isAllowed(), custom expressions
AI Policy Gate
Policies with PENDING or REJECTED approval are filtered out before request-time evaluation
CentralAuditFacade
Records denied URL authorization results with request URI, action, outcome, and optional AI assessment context

Step-by-Step Evaluation

  1. Context refresh triggers CustomDynamicAuthorizationManager.initialize(), which calls PolicyRetrievalPoint.findUrlPolicies().
  2. The manager filters out inactive policies and policies whose approvalStatus is PENDING or REJECTED.
  3. For each remaining URL policy, PolicyExpressionConverter.toExpression(policy) builds the SpEL expression string and ExpressionAuthorizationManagerResolver.resolve(expression) creates the Spring authorization manager.
  4. The resulting RequestMatcherEntry<AuthorizationManager<RequestAuthorizationContext>> list is cached in memory.
  5. At request time, check(...) iterates the cached matchers until it finds the first matching request pattern.
  6. The matched authorization manager evaluates the current Authentication and request context. If no URL policy matches, the default decision is AuthorizationDecision(true).
  7. Denied URL authorization results are recorded through CentralAuditFacade with request URI, action, outcome, and optional AI assessment context before the decision is returned to Spring Security.

Static + Dynamic Integration

Contexa's dynamic authorization coexists with standard Spring Security static rules. Static rules are evaluated first by the filter chain; only requests that reach .anyRequest() are handled by the dynamic authorization manager.

Java
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests(auth -> auth
        // Static rules: evaluated first, bypasses dynamic authorization
        .requestMatchers("/", "/home", "/css/**", "/js/**").permitAll()
        .requestMatchers("/login", "/register").permitAll()
        .requestMatchers("/user/list/**").hasRole("USER")

        // Dynamic authorization: all remaining requests
        // Policies loaded from database, hot-reloadable at runtime
        .anyRequest().access(customDynamicAuthorizationManager)
    );
    return http.build();
}

Evaluation Flow

Incoming Request
GET /admin/dashboard
Static Matchers
/css/**, /js/**, /images/**, /favicon.ico
Match Found?
Yes: return permitAll() immediately | No: continue
CustomDynamicAuthorizationManager
Evaluate XACML policies from database
AuthorizationDecision
Grant or deny based on policy evaluation

Important Behaviors

  • Order matters —Static requestMatchers must be declared before .anyRequest(). Spring Security evaluates them top-to-bottom and uses the first match.
  • Short-circuit evaluation —Once a static matcher matches, the dynamic manager is never invoked. This means permitAll() routes cannot be restricted by database policies.
  • No overlap recommended —Avoid defining database policies for URLs that are already statically matched, as the static rule will always take precedence.
  • Audit implications —Statically permitted requests do not pass through the XACML engine and will not appear in the authorization audit trail.
Caution: If you add a URL to requestMatchers().permitAll(), any database policy targeting that URL will be silently ignored. The static matcher always wins.

Default Decision for Unmatched Requests

When a request URL does not match any policy in the database, CustomDynamicAuthorizationManager returns a default authorization decision. By default, unmatched requests are allowed (AuthorizationDecision(true)), which means only explicitly defined DENY policies block access.

Security consideration: The default-allow behavior is suitable during initial setup and migration. For production environments with strict security requirements, consider creating a catch-all DENY policy with low priority to enforce a deny-by-default posture.

Configuration Patterns

The authorizeHttpRequests block inside the global customizer determines how static matchers interact with the dynamic authorization engine. Choose the pattern that matches your security posture.

Pattern 1: Public Assets + Full Dynamic

The most common pattern. Static assets bypass authorization entirely; everything else is evaluated by the XACML policy engine.

Java
http.authorizeHttpRequests(authReq -> authReq
    .requestMatchers("/css/**", "/js/**", "/images/**", "/favicon.ico").permitAll()
    .anyRequest().access(customDynamicAuthorizationManager)
);

Pattern 2: Admin Fixed + Rest Dynamic

A hybrid approach where certain well-known routes are statically permitted alongside asset paths, while the remaining routes go through dynamic evaluation. Useful when you want guaranteed access to specific pages regardless of policy state.

Java
http.authorizeHttpRequests(authReq -> authReq
    .requestMatchers("/css/**", "/js/**").permitAll()
    .requestMatchers("/home", "/user/list/**").permitAll()
    .anyRequest().access(customDynamicAuthorizationManager)
);

Pattern 3: Full Dynamic

Pattern for routing all unmatched requests through customDynamicAuthorizationManager. In this setup, any route not statically permitted earlier in the chain is evaluated against the runtime policy mappings.

Java
http.authorizeHttpRequests(authReq -> authReq
    .anyRequest().access(customDynamicAuthorizationManager)
);

Pattern Comparison

CriteriaPattern 1: Public Assets + DynamicPattern 2: HybridPattern 3: Full Dynamic
Static asset performanceBest —no policy lookupGood —no policy lookup for assetsSlowest —all requests evaluated
Policy flexibilityHigh for non-asset routesMedium —some routes are hardcodedMaximum —everything is policy-driven
Operational safetySafe —assets always loadSafe —critical pages always reachableRequires careful policy setup
Best forMost applicationsAdmin panels with guaranteed accessEnterprise with full policy governance
Misconfiguration riskLowLowHigh —missing policies block everything

Core Implementation

The manager initializes on Spring context refresh and supports hot-reload for runtime policy updates.

Java
public class CustomDynamicAuthorizationManager
        implements AuthorizationManager<RequestAuthorizationContext> {

    private final PolicyRetrievalPoint policyRetrievalPoint;
    private final ExpressionAuthorizationManagerResolver managerResolver;
    private final ContextHandler contextHandler;
    private final ZeroTrustEventPublisher zeroTrustEventPublisher;
    private final AuthorizationMetrics metricsCollector;

    // Initializes on application context refresh
    @EventListener
    public void onApplicationEvent(ContextRefreshedEvent event) {
        initialize();
    }

    @Override
    public AuthorizationDecision check(
            Supplier<Authentication> authenticationSupplier,
            RequestAuthorizationContext context) {
        // 1. Match request URL to cached RequestMatcherEntry
        // 2. Resolve the SpEL expression for the matched policy
        // 3. Evaluate the expression against the Authentication
        // 4. Record denied results through CentralAuditFacade
        // 5. Return AuthorizationDecision(granted/denied)
    }

    // Hot-reload: clears PRP cache and re-initializes mappings
    public synchronized void reload() {
        policyRetrievalPoint.clearUrlPoliciesCache();
        policyRetrievalPoint.clearMethodPoliciesCache();
        initialize();
    }
}

Policy Model

Each Policy entity stored in the database contains the full authorization definition including targets, rules, and metadata for AI-generated policies.

Java
public class PolicyDto {
    Long id;
    String name;
    String description;
    Policy.Effect effect;
    int priority;
    List targets;
    List rules;
    Policy.PolicySource source;
    Policy.ApprovalStatus approvalStatus;
    Boolean isActive;
    Double confidenceScore;
    String aiModel;
}

Policy Field Reference

FieldTypeDescription
name String A human-readable identifier for the policy, displayed in Policy Center, policy details, and audit output.
description String A human-readable description of what the policy protects and why it exists.
effect Policy.Effect The policy effect. ALLOW grants access when conditions are met; DENY blocks access when conditions are met.
priority int Determines the evaluation order when multiple policies match the same request. Higher priority policies are evaluated first and take precedence.
targets List<TargetDto> The resource targets this policy applies to, defined as Ant-style URL patterns paired with HTTP methods.
rules List<PolicyRule> The authorization rules for this policy. Each rule contains one or more SpEL conditions that are evaluated at request time.
source PolicySource Indicates how the policy was created: MANUAL, AI_GENERATED, AI_EVOLVED, or IMPORTED.
approvalStatus ApprovalStatus The approval workflow status: NOT_REQUIRED, PENDING, APPROVED, or REJECTED. AI-generated policies must be APPROVED before they are enforced.
confidenceScore Double The AI model's confidence score for this policy, ranging from 0.0 to 1.0. Null for manually created policies.
aiModel String The identifier of the AI model that generated this policy. Null for manually created policies.

PolicyTarget

Each target captures a resource type, target identifier, optional HTTP method, ordering, and source type.

Java
public class TargetDto {
    String targetType;         // URL or METHOD
    String targetIdentifier;   // e.g. "/api/admin/**"
    String httpMethod;         // GET, POST, PUT, DELETE, ...
    int targetOrder;
    String sourceType;         // RESOURCE or MANUAL
}

PolicyRule and Conditions

Each rule contains one or more conditions. Conditions hold the actual SpEL expression strings that are evaluated at request time.

Java
public class PolicyRule {
    String name;
    List<PolicyCondition> conditions;
}

public class PolicyCondition {
    String expression;  // SpEL expression, e.g. "hasAnyAuthority('ROLE_ADMIN')"
}

SpEL Expression Support

Contexa extends the standard Spring Security SpEL expressions with custom methods from AbstractAISecurityExpressionRoot for zero-trust AI action checks.

Standard Spring Security Expressions

ExpressionDescription
hasAnyAuthority('ROLE_ADMIN', 'ROLE_USER') Grants access if the principal has any of the listed authorities.
hasAuthority('ROLE_ADMIN') Grants access if the principal has the specified authority.
hasPermission('resource', 'action') Delegates evaluation to the PermissionEvaluator chain. This expression is automatically stripped from URL-level policies since it is only relevant for method-level evaluation.
permitAll Always grants access regardless of authentication state or conditions.
denyAll Always denies access regardless of authentication state or conditions.
isAuthenticated() Grants access if the user is authenticated and not an anonymous user.

Zero-Trust AI Expressions

These expressions are provided by AbstractAISecurityExpressionRoot and integrate with Contexa's zero-trust AI evaluation pipeline.

ExpressionDescription
isAllowed() Returns true if the zero-trust AI action for the current request is ALLOW.
#ai.isBlocked() Returns true if the zero-trust AI action for the current request is BLOCK.
needsChallenge() Returns true if the AI evaluation determined that a step-up MFA challenge is required before granting access.
#ai.needsEscalation() Returns true if the AI evaluation determined that human review or security team escalation is needed.
#ai.isPendingAnalysis() Returns true if asynchronous AI analysis is still in progress for the current request.
#ai.hasAction('ALLOW') Checks whether the zero-trust action matches a specific action by name.

Policy-to-Expression Conversion Rules

The getExpressionFromPolicy() method in CustomDynamicAuthorizationManager applies the following conversion logic:

ConditionResulting Expression
No conditions + ALLOW effect permitAll
No conditions + DENY effect denyAll
Single condition Uses the expression directly
Multiple simple authority conditions hasAnyAuthority('A', 'B', 'C')
Multiple mixed conditions Joined with or operator
DENY effect Wraps the expression in !(expression)
hasPermission() in URL policy Stripped (only relevant for method-level evaluation)

XACML Integration

Dynamic authorization is powered by the XACML engine (eXtensible Access Control Markup Language). The five XACML components work together to manage the complete policy lifecycle. See the XACML Engine page for full details.

ComponentFull NameRole in Dynamic Authorization
PAP Policy Administration Point Creates, reads, updates, and deletes policies through Policy Center, policy detail flows, and related admin services.
PDP Policy Decision Point Evaluates policy conditions as SpEL expressions against the security context and returns a grant or deny decision.
PEP Policy Enforcement Point Implemented by CustomDynamicAuthorizationManager. Enforces PDP decisions within the Spring Security filter chain.
PIP Policy Information Point Provides contextual attributes such as user roles, permissions, and request metadata for use in SpEL expression evaluation.
PRP Policy Retrieval Point Loads policies from the database at application startup and refreshes them on hot-reload events.

Usage Example

The following example shows the complete lifecycle of creating and enforcing a dynamic authorization policy.

Step 1: Create a Policy

Policies can be created through Policy Center and related server-side admin flows. This OSS page does not describe a separate public PAP REST surface.

Java
GET  /admin/policy-center
POST /admin/policy-center/create-policy
POST /admin/policy-center/api/quick-create
POST /admin/api/policies/build-from-business-rule

// Current OSS authoring paths:
// - Policy Center form workflow
// - Quick-create helper inside Policy Center
// - Business-rule helper API for assisted policy generation

Step 2: Policy is Stored in Database

The PAP persists the policy as JPA entities: Policy, PolicyTarget, PolicyRule, and PolicyCondition. For manually created policies, source is set to MANUAL and approvalStatus to NOT_REQUIRED.

Step 3: Manager Picks Up the Policy

On the next hot-reload (or application restart), CustomDynamicAuthorizationManager loads the new policy from the PRP and builds a RequestMatcherEntry mapping:

Text
URL Pattern: /api/admin/**
HTTP Methods: GET, POST, PUT, DELETE
SpEL Expression: hasAuthority('ROLE_ADMIN')
Source: MANUAL
Approval: NOT_REQUIRED (active immediately)

Step 4: Requests are Automatically Secured

When a request arrives at /api/admin/users:

Text
1. Request: GET /api/admin/users
2. CustomDynamicAuthorizationManager.check() is invoked
3. URL matches pattern /api/admin/** (Ant path matching)
4. SpEL expression "hasAuthority('ROLE_ADMIN')" is evaluated
5a. User has ROLE_ADMIN -> AuthorizationDecision(granted=true)
5b. User lacks ROLE_ADMIN -> AuthorizationDecision(granted=false) -> 403
6. If the result is denied, CustomDynamicAuthorizationManager records an audit event with request URI, action, outcome, and available AI context

AI-Generated Policy Example

When an AI model generates a policy, it enters the system with PENDING approval status and is not enforced until a human reviewer approves it.

JSON
{
  "name": "AI: Restrict Sensitive Reports",
  "description": "AI-detected sensitive data endpoint requiring elevated access",
  "effect": "ALLOW",
  "priority": 90,
  "targets": [
    {
      "targetType": "URL",
      "targetIdentifier": "/api/reports/sensitive/**",
      "httpMethod": "GET",
      "targetOrder": 0,
      "sourceType": "RESOURCE",

    }
  ],
  "rules": [
    {
      "name": "Require manager role and AI clearance",
      "conditions": [
        { "expression": "hasAuthority('ROLE_MANAGER')" },
        { "expression": "isAllowed()" }
      ]
    }
  ],
  "source": "AI_GENERATED",
  "approvalStatus": "PENDING",
  "confidenceScore": 0.87,
  "aiModel": "contexa-policy-gen-v2"
}

This policy will be skipped during authorization evaluation until an administrator approves it through the current admin policy review flow.

Configuration

Dynamic authorization is enabled automatically when the contexa-iam module is on the classpath. Runtime policy changes are typically made through Policy Center and related admin policy views, while code-level behavior is defined by the IAM and security configuration classes.

Hot-Reload

After modifying policies through Policy Center or related admin flows, the server calls authorizationManager.reload() to rebuild URL and method policy caches without restarting the application:

Java
policyService.createPolicy(policyDto);
customDynamicAuthorizationManager.reload();

This calls CustomDynamicAuthorizationManager.reload(), which clears the PRP cache and re-initializes all RequestMatcherEntry mappings from the database.

For IAM property configuration, see the Configuration reference. Key properties include:

PropertyDefaultDescription
contexa.iam.admin.rest-docs-path /docs/index.html Base documentation path used when MVC-scanned resources generate their API documentation anchors.
contexa.policy.combining-algorithm FIRST_APPLICABLE Combining algorithm used when multiple URL policies match the same request.
security.zerotrust.mode ENFORCE Controls whether zero-trust decisions are enforced live or observed in shadow mode.

Zero Trust Properties

PropertyDefaultDescription
security.zerotrust.enabledtrueEnable or disable the zero-trust authorization framework globally
security.zerotrust.modeENFORCEAuthorization mode: SHADOW (audit only) or ENFORCE (live enforcement)
security.zerotrust.sampling.rate1.0Sampling rate for authorization evaluation (1.0 = evaluate all requests)
security.zerotrust.hotpath.enabledtrueEnable hot-path optimization for frequently accessed resources
security.zerotrust.threat.initial0.3Initial threat score assigned to new sessions

Threshold Properties

PropertyDefaultDescription
security.zerotrust.thresholds.skip0.3Threat score below which authorization checks may be skipped
security.zerotrust.thresholds.optional0.5Threat score threshold for optional additional verification
security.zerotrust.thresholds.required0.7Threat score threshold requiring mandatory verification
security.zerotrust.thresholds.strict0.9Threat score threshold triggering strict security measures

Cache and Session Properties

PropertyDefaultDescription
security.zerotrust.cache.ttl-hours24TTL for cached authorization decisions (hours)
security.zerotrust.cache.session-ttl-minutes30Session-specific cache TTL (minutes)
security.zerotrust.cache.invalidated-ttl-minutes60TTL for invalidated cache entries before removal (minutes)
security.zerotrust.session.tracking-enabledtrueEnable AI session tracking for behavioral analysis
security.zerotrust.redis.timeout5Redis connection timeout (seconds)
security.zerotrust.redis.update-interval-seconds30Interval for pushing session metrics to Redis
Related: For IAM admin UI properties such as contexa.iam.admin.*, see the Admin Console reference page.

See Also

Default Policy Management

When an incoming request does not match any configured URL policy, CustomDynamicAuthorizationManager.check(...) falls back to new AuthorizationDecision(true). That means unmatched requests are allowed by default in the current OSS runtime.

Java
// Default behavior: unmatched requests are allowed
return new AuthorizationDecision(true);

Moving Toward Default Deny

The OSS codebase does not expose a dedicated YAML property such as default-policy for unmatched URL requests. To move toward deny-by-default, create explicit catch-all DENY policies for protected URL spaces or customize the authorization manager in code.

Creating URL Policies in Policy Center

The current OSS operator flow for URL policies is centered on Policy Center. Follow these steps to create a policy that protects a URL pattern:

  1. Open Policy Center —Go to /admin/policy-center?tab=create and open the integrated create surface.
  2. Choose a create path —Use quick-create for simple role/permission grants, or use the manual form to edit the full PolicyDto.
  3. Set Policy Metadata —Enter a descriptive policy name, select the effect (ALLOW or DENY), and set the priority (lower numbers evaluate first).
  4. Add URL Target —In the Targets section, add a URL target by specifying:
    • URL pattern (e.g., /api/admin/**)
    • HTTP method (GET, POST, PUT, DELETE, or ANY)
  5. Add Rule with Condition —Add a rule containing a SpEL condition expression. For example:
    SpEL
    hasAnyAuthority('ROLE_ADMIN')
  6. Save and Verify —Save the policy through Policy Center or a related admin flow. The server rebuilds authorization mappings through authorizationManager.reload(). Verify enforcement with the list, simulator, impact analysis, and detail views.
Tip: For complex conditions, use Policy Center and the condition catalog to compose stored SpEL rules. #ai.* expressions, role checks, permission checks, time checks, and IP checks can be combined in the same rule.

Policy Hot-Reload

CustomDynamicAuthorizationManager supports hot-reloading of policies without application restart. This ensures authorization changes take effect immediately.

Automatic Reload

Policies are automatically reloaded when the application context fires a ContextRefreshedEvent. The reload process:

  1. PolicyRetrievalPoint fetches the latest policies from the database.
  2. CustomDynamicAuthorizationManager rebuilds the RequestMatcherEntry list.
  3. New requests are evaluated against the updated policy set.

Manual Reload

Reload can be triggered from server-side admin flows that call authorizationManager.reload(), including Policy Center updates and system settings flows. This page does not document a standalone public reload REST endpoint.

Cache Invalidation

When policies are reloaded, CustomDynamicAuthorizationManager.reload() clears URL and method policy caches through PolicyRetrievalPoint and rebuilds request mappings. Shared infrastructure such as Redis may still participate elsewhere in the platform, but the runtime reload contract here is the authorization manager and PRP cache reset.