name: cosec-custom-matcher description: "Use when extending CoSec policy matching with custom ActionMatcher or ConditionMatcher implementations, condition types, matcher factories, ServiceLoader SPI registration, or Spring bean matcher registration."
CoSec Custom Matcher Development
This skill helps you create custom ActionMatcher and ConditionMatcher implementations to extend CoSec's policy evaluation logic. CoSec uses Java SPI (ServiceLoader) to discover matcher factories.
Architecture Overview
Policy matching has two sides:
- ActionMatcher — determines if a request's action (path + method) matches a policy pattern
- ConditionMatcher — determines if contextual conditions are met (user attributes, request properties, etc.)
Both extend RequestMatcher and are created by corresponding factory classes registered via SPI.
Policy
├── condition: ConditionMatcher (policy-level gate)
└── statements[]
├── Statement (effect: DENY)
│ ├── action: ActionMatcher
│ └── condition: ConditionMatcher
└── Statement (effect: ALLOW)
├── action: ActionMatcher
└── condition: ConditionMatcher
Creating a Custom ConditionMatcher
Step 1: Implement the ConditionMatcher
package com.example.cosec.condition
import me.ahoo.cosec.api.context.SecurityContext
import me.ahoo.cosec.api.context.request.Request
import me.ahoo.cosec.api.policy.ConditionMatcher
import me.ahoo.cosec.api.configuration.Configuration
class PremiumUserConditionMatcher(
override val configuration: Configuration
) : ConditionMatcher {
override val type: String = "premiumUser"
override fun match(request: Request, securityContext: SecurityContext): Boolean {
val isPremium = securityContext.principal.attributes["premium"]
return isPremium == "true"
}
}
Key points:
type— unique string identifier used in policy JSONconfiguration— arbitrary key-value config passed from the policy JSONmatch()— returntrueif the condition is satisfied
Step 2: Implement the Factory
package com.example.cosec.condition
import me.ahoo.cosec.api.configuration.Configuration
import me.ahoo.cosec.api.policy.ConditionMatcher
import me.ahoo.cosec.policy.condition.ConditionMatcherFactory
class PremiumUserConditionMatcherFactory : ConditionMatcherFactory {
override val type: String = "premiumUser"
override fun create(configuration: Configuration): ConditionMatcher {
return PremiumUserConditionMatcher(configuration)
}
}
Step 3: Register via SPI
Create file: src/main/resources/META-INF/services/me.ahoo.cosec.policy.condition.ConditionMatcherFactory
com.example.cosec.condition.PremiumUserConditionMatcherFactory
Step 4: Use in Policy JSON
{
"name": "PremiumEndpoints",
"action": "/api/premium/**",
"condition": {
"premiumUser": {}
}
}
With configuration:
{
"name": "TieredAccess",
"action": "/api/**",
"condition": {
"premiumUser": {
"minTier": "gold"
}
}
}
Access configuration in the matcher:
val minTier = configuration.getRequiredString("minTier")
Creating a Custom ActionMatcher
Step 1: Implement the ActionMatcher
package com.example.cosec.action
import me.ahoo.cosec.api.context.SecurityContext
import me.ahoo.cosec.api.context.request.Request
import me.ahoo.cosec.api.policy.ActionMatcher
import me.ahoo.cosec.api.configuration.Configuration
class HttpMethodActionMatcher(
override val configuration: Configuration
) : ActionMatcher {
override val type: String = "httpMethod"
private val allowedMethods: Set<String> = configuration.getRequiredString("methods")
.split(",")
.map { it.trim().uppercase() }
.toSet()
override fun match(request: Request, securityContext: SecurityContext): Boolean {
return request.method.uppercase() in allowedMethods
}
}
Step 2: Implement the Factory
package com.example.cosec.action
import me.ahoo.cosec.api.configuration.Configuration
import me.ahoo.cosec.api.policy.ActionMatcher
import me.ahoo.cosec.policy.action.ActionMatcherFactory
class HttpMethodActionMatcherFactory : ActionMatcherFactory {
override val type: String = "httpMethod"
override fun create(configuration: Configuration): ActionMatcher {
return HttpMethodActionMatcher(configuration)
}
}
Step 3: Register via SPI
Create file: src/main/resources/META-INF/services/me.ahoo.cosec.policy.action.ActionMatcherFactory
com.example.cosec.action.HttpMethodActionMatcherFactory
Step 4: Use in Policy JSON
{
"name": "ReadOnlyAccess",
"action": {
"httpMethod": {
"methods": "GET,HEAD,OPTIONS"
}
}
}
Accessing Configuration Values
The Configuration interface provides typed accessors:
// Required (throws if missing)
val value: String = configuration.getRequiredString("key")
// Optional with default
val value: String = configuration.get("key", "default")
// Nested configuration
val nested: Configuration = configuration.getRequiredConfiguration("nested")
Accessing Request and Context Data
Request properties
request.path // URL path
request.method // HTTP method
request.remoteIp // client IP
request.origin // Origin header
request.referer // Referer header
request.appId // application ID
request.spaceId // space ID
request.deviceId // device ID
request.requestId // request ID
request.getHeader("X-Custom") // any header
request.getQuery("param") // query parameter
request.getCookieValue("name") // cookie value
SecurityContext properties
securityContext.principal // CoSecPrincipal
securityContext.principal.id // user ID
securityContext.principal.authenticated // boolean
securityContext.principal.anonymous // boolean
securityContext.principal.roles // Set<String>
securityContext.principal.policies // Set<String>
securityContext.principal.attributes // Map<String, String>
securityContext.tenant // Tenant info
securityContext.attributes // MutableMap<String, Any>
Built-in ConditionMatcher Types Reference
For reference, here are all built-in types:
| Type | Description | Key Config |
|---|---|---|
authenticated |
User must be logged in | — |
inRole |
User must have role | value: role name |
inTenant |
Must be from tenant | value: tenant ID |
eq |
Exact match | part, value |
contains |
Substring match | part, value |
startsWith |
Prefix match | part, value |
endsWith |
Suffix match | part, value |
in |
Value in list | part, value: array |
regular |
Regex match | part, pattern, negate |
path |
Path pattern match | part, pattern, options |
bool |
Boolean logic | and: array, or: array |
spel |
Spring Expression | expression |
ognl |
OGNL expression | expression |
rateLimiter |
Rate limiting | permitsPerSecond |
groupedRateLimiter |
Grouped rate limit | permitsPerSecond, groupKey |
Built-in ActionMatcher Types Reference
| Type | Description | Key Config |
|---|---|---|
path |
URL path matching | pattern, method, options |
all |
Wildcard | method (optional) |
composite |
OR combination | array of matchers |
Spring Registration (Alternative to SPI)
You can also register matcher factories as Spring beans. The MatcherFactoryRegister auto-configuration picks them up from the ApplicationContext:
@Configuration
class CustomMatcherConfig {
@Bean
fun premiumUserConditionMatcherFactory(): ConditionMatcherFactory {
return PremiumUserConditionMatcherFactory()
}
@Bean
fun httpMethodActionMatcherFactory(): ActionMatcherFactory {
return HttpMethodActionMatcherFactory()
}
}
This approach is simpler when your matcher needs Spring dependencies (e.g., a database or external service).