en un clic
cosec-custom-matcher
// Use when extending CoSec policy matching with custom ActionMatcher or ConditionMatcher implementations, condition types, matcher factories, ServiceLoader SPI registration, or Spring bean matcher registration.
// Use when extending CoSec policy matching with custom ActionMatcher or ConditionMatcher implementations, condition types, matcher factories, ServiceLoader SPI registration, or Spring bean matcher registration.
Use when adding CoSec to a Spring Boot application, choosing WebFlux/WebMVC/Gateway modules, configuring JWT authentication, authorization filters, local policies, Redis policy caching, social login, IP enrichment, OpenAPI, or OpenTelemetry integration.
Use when writing, validating, explaining, or debugging CoSec policy JSON, policy statements, ALLOW/DENY rules, action matchers, condition matchers, role-based authorization, tenant scoping, or rate limiting policies.
Use when diagnosing CoSec authentication or authorization failures such as unexpected 401/403 responses, denied requests that should be allowed, policies not loading, JWT token rejection, matcher mismatches, or unclear access decisions.
| 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. |
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.
Policy matching has two sides:
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
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() — return true if the condition is satisfiedpackage 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)
}
}
Create file: src/main/resources/META-INF/services/me.ahoo.cosec.policy.condition.ConditionMatcherFactory
com.example.cosec.condition.PremiumUserConditionMatcherFactory
{
"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")
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
}
}
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)
}
}
Create file: src/main/resources/META-INF/services/me.ahoo.cosec.policy.action.ActionMatcherFactory
com.example.cosec.action.HttpMethodActionMatcherFactory
{
"name": "ReadOnlyAccess",
"action": {
"httpMethod": {
"methods": "GET,HEAD,OPTIONS"
}
}
}
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")
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.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>
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 |
| Type | Description | Key Config |
|---|---|---|
path | URL path matching | pattern, method, options |
all | Wildcard | method (optional) |
composite | OR combination | array of matchers |
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).