What Are ActionMatchers in CoSec? A Guide to Request Authorization
ActionMatchers in CoSec are pluggable components that determine whether an HTTP request action matches a policy rule by evaluating the request path against configurable patterns.
In the ahoo-wang/cosec authorization framework, ActionMatchers serve as the primary mechanism for identifying what resource a user is attempting to access. They decouple action identification logic from the core policy engine, enabling dynamic, context-aware authorization decisions without modifying core framework code.
The ActionMatcher Interface and Core Contract
At the heart of the system lies the ActionMatcher interface, located in cosec-api/src/main/kotlin/me/ahoo/cosec/api/policy/ActionMatcher.kt. This interface extends the generic RequestMatcher and defines a single, focused contract:
fun match(request: Request, securityContext: SecurityContext): Boolean
The match method receives the incoming Request object—typically containing the HTTP path—and a mutable SecurityContext that holds authentication details and runtime attributes. A return value of true indicates the request action satisfies the policy rule currently being evaluated.
How ActionMatchers Are Created: The Factory Pattern
CoSec instantiates matchers through the ActionMatcherFactory interface (cosec-core/src/main/kotlin/me/ahoo/cosec/policy/action/ActionMatcherFactory.kt). This factory pattern separates configuration from execution, allowing policies to define matchers declaratively in JSON or YAML while the framework handles instantiation.
SPI-Based Discovery
Factories are discovered at runtime using the Java Service Provider Interface (SPI) mechanism. Built-in factories are registered in cosec-core/src/main/resources/META-INF/services/me.ahoo.cosec.policy.action.ActionMatcherFactory. To add a custom matcher, implement ActionMatcherFactory, package it as a JAR, and register your factory class in your own META-INF/services file. CoSec will automatically pick up the implementation using the type string specified in your policy configuration.
Built-in ActionMatcher Implementations
The CoSec codebase provides four primary implementations to handle different matching scenarios:
PathActionMatcher for Static Patterns
PathActionMatcher matches requests against static Spring PathPattern expressions. Located in cosec-core/src/main/kotlin/me/ahoo/cosec/policy/action/PathActionMatcher.kt, this implementation evaluates literal paths such as /api/orders/** and extracts path variables (e.g., {orderId}) into the SecurityContext under the key PATH_VARIABLES for downstream use.
ReplaceablePathActionMatcher for Dynamic SpEL Templates
For scenarios requiring runtime context, ReplaceablePathActionMatcher evaluates patterns as Spring Expression Language (SpEL) templates. This enables dynamic authorization rules such as /tenant/${principal.id}/**, where the ${principal.id} placeholder resolves against values stored in the SecurityContext. This implementation supports multi-tenant and user-specific access controls without hardcoding identifiers into policy files.
CompositeActionMatcher for Complex Logic
When policies require multiple conditions to be satisfied simultaneously, CompositeActionMatcher (cosec-core/src/main/kotlin/me/ahoo/cosec/policy/action/CompositeActionMatcher.kt) aggregates multiple ActionMatcher instances. This implementation effectively performs a logical "AND" operation across its contained matchers, allowing complex rules like "match /api/** AND exclude /api/public/**".
AllActionMatcher for Universal Matching
AllActionMatcher (cosec-core/src/main/kotlin/me/ahoo/cosec/policy/action/AllActionMatcher.kt) is a special pass-through implementation that always returns true. Use this matcher when a policy should apply regardless of the request action, effectively ignoring the action dimension while still evaluating subject and condition constraints.
Policy Evaluation Flow
When CoSec evaluates an authorization request, ActionMatchers participate in a specific sequence:
- The policy JSON supplies an
actionnode (e.g.,{ "action": { "type": "path", "pattern": "/api/**" } }). - CoSec deserializes this node into a
Configurationobject. - The corresponding
ActionMatcherFactory.create(configuration)method instantiates the appropriate matcher. - During policy evaluation, the framework invokes
matcher.match(request, securityContext). - If the matcher succeeds and the pattern contains path variables, these variables are stored in the
SecurityContextunderPATH_VARIABLESfor later retrieval by condition matchers or business logic.
Practical Implementation Examples
The following Kotlin examples demonstrate how to construct and use ActionMatchers programmatically:
// Build a static path matcher from configuration
val json = """{ "type": "path", "pattern": "/api/orders/**" }"""
val cfg = json.asConfiguration()
val matcher = PathActionMatcherFactory.INSTANCE.create(cfg)
// Use the matcher during a request
val request = SimpleRequest(path = "/api/orders/123")
val ctx = SimpleSecurityContext()
val matches = matcher.match(request, ctx)
println(ctx.getPathVariables()) // { "orderId" -> "123" }
For dynamic, context-aware matching:
// Dynamic matcher with SpEL template
val dynJson = """{ "type": "path", "pattern": "/tenant/${'$'}{principal.id}/**" }"""
val dynCfg = dynJson.asConfiguration()
val dynMatcher = PathActionMatcherFactory.INSTANCE.create(dynCfg)
// Assume the principal id is "42" in the security context
ctx.setAttributeValue("principal.id", "42")
val dynMatches = dynMatcher.match(request, ctx)
Extending ActionMatchers with Custom Implementations
The SPI-based architecture enables seamless extension without forking the core codebase. To implement a custom ActionMatcher:
- Create a class implementing the
ActionMatcherinterface. - Create a factory class implementing
ActionMatcherFactorythat returns your matcher instance. - Register your factory in
META-INF/services/me.ahoo.cosec.policy.action.ActionMatcherFactory. - Reference your matcher in policy files using the
typestring returned by your factory'sgetType()method.
This design pattern ensures that domain-specific matching logic—such as matching against GraphQL operation names or gRPC method descriptors—can be added as first-class citizens within the CoSec authorization framework.
Summary
- ActionMatchers are the core abstraction in
ahoo-wang/cosecfor determining if a request action satisfies policy requirements. - The
ActionMatcherinterface defines a singlematch(request, securityContext)method implemented by all concrete matchers. - PathActionMatcher handles static URL patterns while ReplaceablePathActionMatcher supports dynamic SpEL templates for context-aware routing.
- The SPI factory pattern enables zero-code-integration of custom matchers by registering implementations in
META-INF/services. - Successful matches extract path variables into the
SecurityContextunderPATH_VARIABLESfor downstream policy evaluation.
Frequently Asked Questions
What is the difference between PathActionMatcher and ReplaceablePathActionMatcher?
PathActionMatcher evaluates static Spring PathPattern expressions literally, making it ideal for fixed API routes like /api/users/**. ReplaceablePathActionMatcher treats the pattern as a SpEL template, allowing placeholders like ${principal.id} to be resolved at runtime against the SecurityContext, which supports dynamic multi-tenant scenarios.
How does CoSec discover custom ActionMatcher implementations?
CoSec uses the Java SPI mechanism to locate ActionMatcherFactory implementations at runtime. Custom matchers must package a service registration file at META-INF/services/me.ahoo.cosec.policy.action.ActionMatcherFactory containing the fully qualified class name of their factory implementation.
Can ActionMatchers extract variables from the request path?
Yes. When using PathActionMatcher or ReplaceablePathActionMatcher with templated segments (e.g., /orders/{orderId}), successfully matched path variables are automatically stored in the SecurityContext under the PATH_VARIABLES key, making them available to condition matchers and downstream authorization logic.
What happens when multiple ActionMatchers need to be combined?
Use CompositeActionMatcher to aggregate multiple matchers into a single logical unit. This implementation requires all contained matchers to return true for the composite to match, effectively providing an "AND" semantic for complex authorization rules that must satisfy multiple path conditions simultaneously.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →