| 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 JSON
configuration — arbitrary key-value config passed from the policy JSON
match() — return true if 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:
val value: String = configuration.getRequiredString("key")
val value: String = configuration.get("key", "default")
val nested: Configuration = configuration.getRequiredConfiguration("nested")
Accessing Request and Context Data
Request properties
request.path
request.method
request.remoteIp
request.origin
request.referer
request.appId
request.spaceId
request.deviceId
request.requestId
request.getHeader("X-Custom")
request.getQuery("param")
request.getCookieValue("name")
SecurityContext properties
securityContext.principal
securityContext.principal.id
securityContext.principal.authenticated
securityContext.principal.anonymous
securityContext.principal.roles
securityContext.principal.policies
securityContext.principal.attributes
securityContext.tenant
securityContext.attributes
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).