Fastify项目中基于API密钥有效性的条件化速率限制问题
解决方案:基于API密钥有效性的动态路由限流
你遇到的核心问题是无效API密钥的客户端共享了同一个限流计数器,因为keyGenerator对所有无效密钥都返回'false',导致一个客户端触发限流后,其他无效密钥的客户端也会被限制。通过设置global: false搭配preHandler钩子,可以实现按客户端单独计数的动态限流,具体实现如下:
步骤1:修改限流插件注册配置
关闭全局限流,仅在需要的路由上启用限流:
await fastify.register(fastifyRateLimit, { timeWindow: '1 minute', // 保持时间窗口配置 global: false, // 禁用全局限流,改为路由级按需配置 });
步骤2:编写前置处理钩子(preHandler)
创建一个钩子函数,提前验证API密钥有效性,并为当前请求设置专属的限流标识和阈值:
async function rateLimitPreHandler(request) { const apiKey = request.headers['x-api-key']; let isValidApiKey = false; if (apiKey) { // 复用你的验证逻辑,注意转换为布尔值 const validateResult = await validateToken(fastify, apiKey); isValidApiKey = validateResult === 'true'; } // 为限流生成唯一key: // - 有效密钥:用密钥本身作为标识(同一密钥共享限流) // - 无效密钥:用客户端IP+UA作为标识(每个客户端单独计数) request.rateLimitKey = isValidApiKey ? apiKey : `${request.ip}-${request.headers['user-agent'] || 'unknown'}`; // 设置对应限流阈值 request.rateLimitMax = isValidApiKey ? 1000 : 1; }
步骤3:在目标路由上应用限流
在需要限流的路由中,通过config.rateLimit配置限流规则,读取preHandler中设置的参数:
// 示例:GET / 路由 fastify.get('/', { preHandler: rateLimitPreHandler, config: { rateLimit: { keyGenerator: (request) => request.rateLimitKey, max: (request) => request.rateLimitMax, }, }, }, async (request, reply) => { return { hello: 'world' }; }); // 示例:POST /test 路由 fastify.post('/test', { preHandler: rateLimitPreHandler, config: { rateLimit: { keyGenerator: (request) => request.rateLimitKey, max: (request) => request.rateLimitMax, }, }, }, async (request, reply) => { // 可以直接复用preHandler的验证结果,无需重复调用validateToken const isValid = request.rateLimitKey === request.headers['x-api-key']; console.log('API key valid:', isValid); return { foo: 'bar' }; });
关键逻辑说明
- 无效客户端单独计数:通过
IP+UA作为无效密钥的限流标识,确保每个客户端的限流计数器独立,不会互相影响。 - 动态阈值配置:根据密钥有效性,分别设置1000次/分钟和1次/分钟的限流阈值。
- 避免重复验证:
preHandler中提前完成密钥验证,路由处理逻辑可以直接复用结果,提升性能。
内容的提问来源于stack exchange,提问作者stefano manzi
相关产品推荐
相关产品推荐

