Reactive Design
CoSec is built from the ground up on Project Reactor. Every authorization and authentication operation returns Mono<T>, enabling fully non-blocking security evaluation. This page traces the reactive data flow from the incoming HTTP request through the filter chain, authorization pipeline, and error handling.
Core Reactive Contracts
The foundation of CoSec's reactive design is the Authorization fun interface (Authorization.kt:35), which returns Mono<AuthorizeResult>:
fun interface Authorization {
fun authorize(
request: Request,
context: SecurityContext
): Mono<AuthorizeResult>
}Similarly, the Authentication interface (Authentication.kt:32) returns Mono<out P>:
interface Authentication<C : Credentials, out P : CoSecPrincipal> {
fun authenticate(credentials: C): Mono<out P>
}Both contracts ensure the caller never blocks. The reactive types propagate through the entire filter chain, from Spring's WebFilter or GlobalFilter down to repository lookups.
Reactive Filter Chain
The central reactive integration point is ReactiveSecurityFilter (ReactiveSecurityFilter.kt:57), which is extended by both ReactiveAuthorizationFilter (WebFlux) and AuthorizationGatewayFilter (Spring Cloud Gateway).
sequenceDiagram
autonumber
participant Client
participant WebFilter as ReactiveAuthorizationFilter
participant RSF as ReactiveSecurityFilter
participant Authz as Authorization
participant Chain as FilterChain
Client->>WebFilter: filter(exchange, chain)
WebFilter->>RSF: filterInternal(exchange, chainFn)
RSF->>RSF: requestParser.parse(exchange)
RSF->>RSF: securityContextParser.parse(request)
Note over RSF: Catch TokenVerificationException -> anonymous context
RSF->>RSF: exchange.setSecurityContext(securityContext)
RSF->>Authz: authorize(request, securityContext)
Authz-->>RSF: Mono<AuthorizeResult>
RSF->>RSF: flatMap { evaluate result }
alt Authorized
RSF->>Chain: chain.filter(exchange)
Chain-->>RSF: Mono<Void>
RSF->>RSF: writeSecurityContext(securityContext)
RSF-->>Client: Continue processing
else Unauthenticated
RSF->>RSF: response.statusCode = 401
RSF-->>Client: HTTP 401 Unauthorized
else Forbidden
RSF->>RSF: response.statusCode = 403
RSF-->>Client: HTTP 403 Forbidden
endThe filterInternal Method
The filterInternal method (ReactiveSecurityFilter.kt:66-116) is the reactive pipeline entry point. Its structure is:
- Parse the request from the
ServerWebExchangeusing the injectedRequestParser(line 70). - Parse the security context using the injected
SecurityContextParser. If aTokenVerificationExceptionoccurs, catch it and fall back to an anonymous context (line 73-81). - Authorize by calling
authorization.authorize(request, securityContext)(line 85). - FlatMap on the result: if authorized, mutate the exchange with the principal and invoke the filter chain; otherwise set 401 or 403 (line 87-105).
- onErrorResume for
TooManyRequestsExceptionreturns HTTP 429 (line 106). - onErrorResume for unexpected errors returns HTTP 500 with
IMPLICIT_DENY(line 109).
Mono Chain in Authorization
The SimpleAuthorization class (SimpleAuthorization.kt:48) builds a reactive chain using Mono.switchIfEmpty to cascade through authorization stages:
flowchart LR
VERIFY["verify(request, context)"]
GLOBAL["verifyGlobalPolicies()"]
PRINCIPAL["verifyPrincipalPolicies()"]
ROLE["verifyAppRolePermission()"]
MAP["map to AuthorizeResult"]
DENY["IMPLICIT_DENY"]
VERIFY --> GLOBAL
GLOBAL -->|"switchIfEmpty"| PRINCIPAL
PRINCIPAL -->|"switchIfEmpty"| ROLE
ROLE --> MAP
MAP -->|"switchIfEmpty"| DENY
style VERIFY fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style GLOBAL fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style PRINCIPAL fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style ROLE fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style MAP fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
style DENY fill:#2d333b,stroke:#6d5dfc,color:#e6edf3The verify method (SimpleAuthorization.kt:194-211) constructs this chain:
verifyGlobalPolicies(request, context)
.switchIfEmpty { verifyPrincipalPolicies(request, context) }
.switchIfEmpty { verifyAppRolePermission(request, context) }
.map { context.setVerifyContext(it); it.result.toAuthorizeResult() }
.switchIfEmpty { AuthorizeResult.IMPLICIT_DENY.toMono() }Each verifyXxx method returns Mono<VerifyContext>. If a method produces an empty Mono (no matching policy), switchIfEmpty triggers the next stage. This is semantically equivalent to a series of orElse calls but in a non-blocking reactive style.
The outer authorize method (SimpleAuthorization.kt:213-232) wraps the verify chain with synchronous root user check and an asynchronous blacklist check:
override fun authorize(request: Request, context: SecurityContext): Mono<AuthorizeResult> {
val verifyResult = verifyRoot(context) // synchronous
if (verifyResult == VerifyResult.ALLOW) {
return AuthorizeResult.ALLOW.toMono() // short-circuit
}
return blacklistChecker.check(request, context) // Mono<Boolean>
.flatMap { allowed ->
if (!allowed) return@flatMap AuthorizeResult.EXPLICIT_DENY.toMono()
verify(request, context) // cascade chain
}
}Integration Variants
Three integration modules consume the reactive authorization pipeline, each adapting to a different Spring runtime.
classDiagram
class ReactiveSecurityFilter {
<<abstract>>
+securityContextParser: SecurityContextParser
+requestParser: RequestParser~ServerWebExchange~
+authorization: Authorization
+filterInternal(exchange, chain): Mono~Void~
}
class ReactiveAuthorizationFilter {
+filter(exchange, chain): Mono~Void~
+getOrder(): Int = 1000
}
class AuthorizationGatewayFilter {
+filter(exchange, chain): Mono~Void~
+getOrder(): Int = HIGHEST_PRECEDENCE + 10
}
class Authorization {
<<interface>>
}
class WebFilter {
<<interface>>
}
class GlobalFilter {
<<interface>>
}
ReactiveSecurityFilter <|-- ReactiveAuthorizationFilter
ReactiveSecurityFilter <|-- AuthorizationGatewayFilter
WebFilter <|.. ReactiveAuthorizationFilter
GlobalFilter <|.. AuthorizationGatewayFilter
Ordered <|.. ReactiveAuthorizationFilter
Ordered <|.. AuthorizationGatewayFilterReactiveAuthorizationFilter (WebFlux)
The WebFlux variant (ReactiveAuthorizationFilter.kt:36) implements WebFilter and delegates to filterInternal. It runs at order 1000 to execute after framework filters (CORS, etc.) but before application filters.
AuthorizationGatewayFilter (Spring Cloud Gateway)
The Gateway variant (AuthorizationGatewayFilter.kt:31) implements Spring Cloud Gateway's GlobalFilter. It runs at HIGHEST_PRECEDENCE + 10 to authorize very early in the gateway filter chain. After successful authorization, it mutates the request to propagate the requestId header downstream (line 47-50).
Error Handling Strategy
The reactive pipeline uses onErrorResume operators at two levels to ensure graceful degradation:
sequenceDiagram
autonumber
participant RSF as ReactiveSecurityFilter
participant Authz as Authorization
participant Error as Error Handler
RSF->>Authz: authorize(request, context)
alt Success path
Authz-->>RSF: Mono<AuthorizeResult>
RSF->>RSF: flatMap { evaluate }
else TooManyRequestsException
Authz-->>RSF: Error(TooManyRequestsException)
RSF->>Error: onErrorResume(TooManyRequestsException)
Error-->>RSF: HTTP 429 + TOO_MANY_REQUESTS
else Unexpected Exception
Authz-->>RSF: Error(RuntimeException)
RSF->>Error: onErrorResume { generic }
Error-->>RSF: HTTP 500 + IMPLICIT_DENY
endThe error handling is layered in filterInternal (ReactiveSecurityFilter.kt:106-114):
TokenVerificationException -- Caught synchronously during context parsing (line 75). The request proceeds as anonymous. If the anonymous user is later denied, the response is 401 (not 403).
TooManyRequestsException -- Caught via
onErrorResume(TooManyRequestsException::class.java)(line 106). Returns HTTP 429 with aTOO_MANY_REQUESTSresult body. This is typically raised by rate-limitingConditionMatcherimplementations.Generic Exception -- Caught by the final
onErrorResumeblock (line 109). Logged at ERROR level and returns HTTP 500 withIMPLICIT_DENY. This ensures the security framework never crashes the application; it defaults to deny access on unexpected failures.
All error responses are serialized as JSON using CoSecJsonSerializer and written as a Mono<Void> via writeWithAuthorizeResult (line 118).
References
- Authorization.kt --
Mono<AuthorizeResult>contract - Authentication.kt --
Mono<out P>contract - ReactiveSecurityFilter.kt -- Base reactive filter with error handling
- ReactiveAuthorizationFilter.kt -- WebFlux WebFilter adapter
- AuthorizationGatewayFilter.kt -- Gateway GlobalFilter adapter
- SimpleAuthorization.kt --
Mono.switchIfEmptyauthorization cascade
Related Pages
- Security Model -- The policy evaluation algorithm behind the reactive chain
- Module Dependency Graph -- How WebFlux, Gateway, and WebMVC modules are structured
- Multi-Tenancy -- How tenant context flows through the reactive pipeline