必须将租户标识注入限流键生成逻辑,否则不同租户共用同一速率限制窗口;需在routeserviceprovider中通过ratelimiter::for()或自定义tenantawarethrottlekeygenerator实现租户隔离,并验证redis中存在带租户id的rate_limit_api:*键。

在多租户SaaS应用中,Laravel限流器默认使用全局键(如 rate_limit_api),会导致不同租户共用同一速率限制窗口——A公司用户刷爆配额后,B公司用户立刻被拦,严重破坏租户间业务独立性。必须将租户标识注入限流键生成逻辑,否则隔离形同虚设。
确认当前限流器是否已绑定租户上下文
打开 app/Providers/RouteServiceProvider.php,检查 boot() 方法内是否已注册租户感知的限流器。若未见类似 RateLimiter::for('api', function ($request) { ... }) 中调用 Tenant::current()?->id 或 tenant()->id 的逻辑,则说明限流器仍处于“裸奔”状态,所有租户共享同一计数器。
这一步不验证就动手改配置,后续所有限流策略都会失效。
方法一:通过 RateLimiter::for() 注入租户ID
在 app/Providers/RouteServiceProvider.php 的 boot() 方法中,找到或新增限流定义:
① 定位到 RateLimiter::for('api', function ($request) { 这一行;
② 将原本的 return Limit::perMinute(60); 替换为:
return Limit::perMinute(60)->by($request->user()?->tenant_id ?? Tenant::current()?->id ?? 'unknown');
⚠️ 注意:【Tenant::current()?->id 必须在租户中间件执行后才可用】,确保该限流器定义位于 $this->configureRateLimiting(); 调用之后,且租户识别中间件(如 InitializeTenancyByDomain)已在 Kernel.php 中正确注册于 web 和 api 中间件组之前。
③ 保存文件,清除配置缓存:php artisan config:clear。
方法二:自定义限流键生成器(推荐用于复杂场景)
当需支持子域名、请求头、路由参数多种租户识别方式,或需兼容未登录用户时,直接修改 RateLimiter::for() 易失控。此时应接管键生成逻辑:
第一步:在 app/Support/TenantAwareThrottleKeyGenerator.php 中创建新类:
use Illuminate\Support\Str; class TenantAwareThrottleKeyGenerator { public static function generate($request, $limitName) { $tenantId = request()->route('tenant') ?? $request->header('X-Tenant-ID') ?? Tenant::current()?->id ?? Str::slug($request->getHost()); return $limitName . ':' . $tenantId; } }
第二步:在 app/Providers/RouteServiceProvider.php 中重写 configureRateLimiting() 方法:
protected function configureRateLimiting() { RateLimiter::via(function ($request) { return TenantAwareThrottleKeyGenerator::generate($request, 'api'); }); }
第三步:确保 Tenant::current() 在此阶段已初始化——若使用 stancl/tenancy,需确认 InitializeTenancyByDomain 等中间件已在 app/Http/Kernel.php 的 $middlewareGroups['api'] 中置于最前。
验证限流键是否真正分租户
启动 Tinker:php artisan tinker;
执行:Cache::store('redis')->keys('rate_limit_api:*');
观察返回结果——若看到多个键如 rate_limit_api:acme_corp、rate_limit_api:beta_inc,说明租户前缀已生效;若仅见 rate_limit_api:global 或无租户标识,则说明租户上下文未注入成功,需回溯中间件加载顺序与 Tenant::current() 初始化时机。











