I took my rate limiter out of my Spring BFF, and put it into my Spring Rest-API as I wanted it to be user specific (and apply different rate limits, depending on the class of user)
So I created my rate limiter.
Rate Limiter
@Component
internal class RequestRateLimiterConfig(
private val redisRateLimiter: RedisRateLimiter,
private val defaultKeyResolver: KeyResolver
) : AbstractGatewayFilterFactory<RequestRateLimiterConfig.Config>(Config::class.java) {
companion object {
const val RATE_LIMITER_ID = "redis-rate-limiter"
}
private val logger = LoggerFactory.getLogger(RequestRateLimiterConfig::class.java)
override fun apply(config: Config): GatewayFilter {
return GatewayFilter { exchange: ServerWebExchange, chain ->
logger.info("Entering rate limiter filter...")
val keyMono = defaultKeyResolver.resolve(exchange)
keyMono
.flatMap { key ->
if (key.isNullOrEmpty()) {
logger.warn("No key resolved. Blocking request.")
LocalExceptionHandlers.missingKey(exchange)
} else {
logger.info("Resolved key: $key")
redisRateLimiter.isAllowed(RATE_LIMITER_ID, key)
.flatMap { response ->
if (!response.isAllowed) {
logger.warn("Rate limit exceeded for key: $key")
LocalExceptionHandlers.rateLimitExceeded(exchange)
} else {
logger.info("Rate limit allowed for key: $key")
chain.filter(exchange)
}
}
}
}.then()
}
}
override fun newConfig(): Config {
return Config()
}
class Config
}
/**
* More Rate Limiter configuration
*/
@Configuration
internal class RedisRateLimiterConfig(
private val authorizationProperties: AuthorizationProperties,
) {
private val logger = LoggerFactory.getLogger(RedisRateLimiterConfig::class.java)
/**
* Redis Rate Limited
*/
@Bean
fun redisRateLimiter(): RedisRateLimiter {
return RedisRateLimiter(1, 1, 1)
}
/**
* Default Key Resolver
*/
@Bean
fun defaultKeyResolver(): KeyResolver {
return KeyResolver { exchange: ServerWebExchange ->
// fetch authentication context and resolve provider ID if available
ReactiveSecurityContextHolder.getContext()
.doOnNext { context -> logger.info("Security context found: ${context.authentication}") }
.mapNotNull { it.authentication as? JwtAuthenticationToken }
.flatMap { authentication ->
val providerId = authentication?.token?.getClaimAsString(authorizationProperties.authProviderSubjectClaim)
if (providerId.isNullOrBlank()) {
logger.warn("No provider ID found in token claims.")
Mono.just("")
} else {
logger.info("Resolved provider ID for Rate Limiting: $providerId")
Mono.just(providerId)
}
}
.switchIfEmpty(
Mono.defer {
logger.warn("No authentication found in security context.")
Mono.just("")
}
)
}
}
}
But nothing was triggering, I could not see any logs.
So I added this (so all routes are routed to this same server).
Note, I do not think this is ideal in a load balancer environment, as the request has already reached the resource server, and this is effectively forwarding it back out to itself.
But even with this, I get no logged information. I send a request to a hello world endpoint, and I get all 25 back very quickly in < 1 second (when the rate limiter should really kick in after 1 request)
Route Builder
@Configuration
internal class RoutingConfig(
private val serverProperties: ServerProperties,
private val rateLimitingFilter: RequestRateLimiterConfig
) {
private val logger = LoggerFactory.getLogger(RoutingConfig::class.java)
@Bean
fun routeLocator(builder: RouteLocatorBuilder): RouteLocator {
return builder.routes()
.route("resource-server") { r ->
r.path("/**") // Match all paths
.filters { f ->
logger.info("Entering resource-server route")
f.filter(rateLimitingFilter.apply(rateLimitingFilter.newConfig()))
}
.uri(serverProperties.resourceServerUri)
}
.build()
}
}
Yaml
Here is my yaml config
# default spring settings
spring:
# spring cloud settings
cloud:
# spring gateway settings
gateway:
metrics:
enabled: true
tags:
path:
enabled: true
OAuth Resource Server
And my resource server security chain is quite standard:
I took my rate limiter out of my Spring BFF, and put it into my Spring Rest-API as I wanted it to be user specific (and apply different rate limits, depending on the class of user)
So I created my rate limiter.
Rate Limiter
But nothing was triggering, I could not see any logs.
So I added this (so all routes are routed to this same server).
Note, I do not think this is ideal in a load balancer environment, as the request has already reached the resource server, and this is effectively forwarding it back out to itself.
But even with this, I get no logged information. I send a request to a hello world endpoint, and I get all 25 back very quickly in < 1 second (when the rate limiter should really kick in after 1 request)
Route Builder
Yaml
Here is my yaml config
OAuth Resource Server
And my resource server security chain is quite standard:
I'm not sure where I've gone wrong? Maybe its the yaml?
Would appreciate any help