Spring Cloud Gateway按用户类别实现限流?或改用AWS API Gateway?
问题
我正在AWS基础设施中实现限流功能,应用请求目前通过Spring Cloud Gateway(已实现熔断、重试、限流及超时控制)处理。当前实现基于请求中的session cookie ID作为Key进行限流,相关代码如下:
限流配置类
@Configuration internal class RequestRateLimiterConfig( private val requestRateLimiterGatewayFilterFactory: RequestRateLimiterGatewayFilterFactory, private val redisRateLimiter: RedisRateLimiter, private val defaultKeyResolver: KeyResolver ) { private val logger = LoggerFactory.getLogger(RequestRateLimiterConfig::class.java) @Bean fun requestRateLimiter(): GlobalFilter { // rate limiter filter val rateLimiterConfig = RequestRateLimiterGatewayFilterFactory.Config().apply { rateLimiter = redisRateLimiter keyResolver = defaultKeyResolver denyEmptyKey = true statusCode = HttpStatus.TOO_MANY_REQUESTS emptyKeyStatus = HttpStatus.BAD_REQUEST.name } val rateLimiterFilter = requestRateLimiterGatewayFilterFactory.apply(rateLimiterConfig) return GlobalFilter { exchange, chain -> val keyMono = defaultKeyResolver.resolve(exchange) keyMono .flatMap { key -> if (key.isNullOrEmpty()) { // if key is null or empty, return error logger.warn("Empty session ID detected. Sending error response.") return@flatMap LocalExceptionHandlers.missingKey(exchange) } else { // if key is present, continue with rate limiting logger.info("Resolved key: $key") return@flatMap rateLimiterFilter.filter(exchange, chain) .onErrorResume { e -> // Handle rate limiting errors and send error response val status = exchange.response.statusCode if (status == HttpStatus.TOO_MANY_REQUESTS) { return@onErrorResume LocalExceptionHandlers.rateLimitExceeded(exchange) } Mono.error(e) } } }.then() } } }
Redis限流Bean
/** * Redis Rate Limited */ @Bean fun redisRateLimiter(): RedisRateLimiter { return RedisRateLimiter(10, 20, 1) }
KeyResolver实现
/** * Default Key Resolver */ @Bean fun defaultKeyResolver(): KeyResolver { return KeyResolver { exchange: ServerWebExchange -> val sessionId = exchange.request.cookies[sessionProperties.SESSION_COOKIE_NAME]?.first()?.value if (sessionId.isNullOrBlank()) { logger.warn("No session ID found in cookie.") Mono.just("") } else { logger.info("Resolved session ID for Rate Limiting: $sessionId") Mono.justOrEmpty(sessionId) } } }
现在我希望按用户类别(如premium、basic、admin_user、normal_user)设置不同的限流规则,请问如何通过Spring Cloud Gateway实现该需求?我了解到AWS API Gateway的Usage Plans功能可实现限流,但同时使用两个网关并不合理。另外,我的Spring Cloud Gateway已集成Auth0 by Okta授权,会获取access token并存储至Redis,理论上可在SCG中验证token获取用户角色以使用不同限流规则,但有人指出双重验证access token是不良实践。我是否应改用AWS API Gateway?恳请提供解决方案建议。
解决方案建议
1. 在Spring Cloud Gateway内实现多规则限流(无需双重验证token)
既然你的SCG已经完成Auth0授权并将access token存储到Redis,无需重复验证token签名,只需直接获取已解析的用户角色即可:
- 扩展KeyResolver,加入角色维度
修改KeyResolver,将用户ID+角色作为限流Key(格式如user123:premium),既保证同一用户不同角色的限流规则区分,也能独立统计每个用户在对应角色下的请求次数:@Bean fun roleBasedKeyResolver(sessionProperties: SessionProperties, redisTemplate: StringRedisTemplate): KeyResolver { return KeyResolver { exchange -> val sessionId = exchange.request.cookies[sessionProperties.SESSION_COOKIE_NAME]?.first()?.value if (sessionId.isNullOrBlank()) { logger.warn("No session ID found in cookie.") return@KeyResolver Mono.just("") } // 从Redis读取已缓存的用户角色(假设缓存key为"auth:session:$sessionId:role") redisTemplate.opsForValue().getMono("auth:session:$sessionId:role") .defaultIfEmpty("normal_user") .map { role -> "$sessionId:$role" } } } - 自定义RedisRateLimiter,按角色加载限流规则
继承默认RedisRateLimiter,重写限流逻辑,根据角色从配置文件加载对应参数:class RoleBasedRedisRateLimiter : RedisRateLimiter(0, 0, 0) { @Value("\${rate.limits.premium.replenishRate:30}") private var premiumReplenishRate: Int = 30 @Value("\${rate.limits.premium.burstCapacity:60}") private var premiumBurstCapacity: Int = 60 @Value("\${rate.limits.basic.replenishRate:10}") private var basicReplenishRate: Int = 10 @Value("\${rate.limits.basic.burstCapacity:20}") private var basicBurstCapacity: Int = 20 override fun isAllowed(id: String, config: Config): Mono<Response> { // 从限流Key中拆分出角色 val role = id.split(":").last() val (replenishRate, burstCapacity) = when(role) { "premium" -> Pair(premiumReplenishRate, premiumBurstCapacity) "basic" -> Pair(basicReplenishRate, basicBurstCapacity) "admin_user" -> Pair(100, 200) else -> Pair(5, 10) // normal_user默认规则 } // 使用对应角色的参数执行限流判断 return super.isAllowed(id, Config().apply { this.replenishRate = replenishRate this.burstCapacity = burstCapacity }) } } - 更新全局Filter配置
将自定义的RoleBasedRedisRateLimiter和roleBasedKeyResolver注入到RequestRateLimiterConfig,替换原有的默认实现即可。
2. 关于双重验证token的规避方案
“双重验证access token是不良实践”的说法合理,你可以通过以下方式避免重复验证:
- 在Auth0授权过滤器中,将解析后的用户角色直接存入
ServerWebExchange的属性中,后续限流逻辑直接从属性读取,无需访问Redis。 - 如果已将token信息缓存到Redis,直接读取缓存值即可,无需重新解析token签名。
3. 是否需要改用AWS API Gateway?
不建议同时使用两个网关,原因如下:
- 增加架构复杂度:双网关会带来额外运维成本、请求延迟和故障点。
- 功能重叠:SCG已具备限流、熔断等核心能力,完全可以通过扩展实现多角色限流需求。
- 迁移成本高:你已在SCG集成Auth0授权,迁移到AWS API Gateway需要重新配置授权规则,增加不必要的工作量。
如果后续有AWS生态强依赖需求(如与Lambda、CloudWatch深度集成),可考虑逐步迁移,但当前场景下优先扩展SCG实现更高效。
内容的提问来源于stack exchange,提问作者Sachin
相关产品推荐
相关产品推荐

