laravel api 路由中 throttle 中间件不生效,因默认依赖 session 而 api 路由关闭了 startsession;应自定义 throttlerequests 的 resolverequestsignature() 方法,优先用 bearertoken()、fallback 到 ip,并重写 buildresponse() 返回 json 格式 429 响应及 retry-after 头。

为什么 Laravel 的 throttle 中间件在 API 路由里不生效
因为默认的 throttle 中间件依赖 session,而 API 路由通常关闭了 session 启动(api 中间件组默认不含 StartSession)。没 session,Laravel 就没法按用户维度识别请求来源,限流直接退化成“每秒最多 X 次”,且无法区分不同客户端。
- 检查你的路由是否在
routes/api.php里,并被api中间件组包裹 —— 这是问题高发场景 - 不要手动加
StartSession到 API 路由:API 应无状态,加 session 会破坏无状态性,还可能引发跨域或并发问题 - 正确做法是改用基于请求标识(如 IP、token、header)的限流策略,跳过 session 依赖
如何用 ThrottleRequests 自定义 key 实现 API 限流
Laravel 的 ThrottleRequests 中间件支持重写 resolveRequestSignature() 方法来定义限流 key。API 场景下,最常用的是从 Authorization header 提取 token(对应用户),或 fallback 到 IP。
- 在
app/Http/Middleware/ApiThrottleRequests.php中继承ThrottleRequests - 重写
resolveRequestSignature():优先取$request->bearerToken(),为空则用$request->ip() - 注意:Bearer Token 若来自第三方 OAuth(如 Passport),需确保已解码并验证有效性;否则攻击者可伪造 token 绕过限流
- 示例 key 返回值:
return $request->bearerToken() ?: $request->ip();
throttle:60,1,by=ip 这种写法在 API 里为什么危险
它强制按 IP 限流,看似简单,但实际在 NAT 环境(公司网络、校园网、移动运营商出口)下,几十甚至上百用户共享同一个公网 IP,极易误伤正常用户。
- 别在面向用户的 API 接口上直接用
by=ip,尤其登录、注册、短信验证码等高频敏感接口 - 如果必须用 IP 作为兜底(比如未带 token 的访客请求),应单独路由分组,并设置更宽松阈值(如
throttle:10,1) - Redis 后端下,IP 限流 key 是
throttle:ip:xxx.xxx.xxx.xxx,可通过redis-cli keys "throttle:ip:*"快速确认是否堆积异常
自定义限流响应格式与状态码怎么统一处理
Laravel 默认返回 HTML 页面(含 429 状态),但 API 客户端只认 JSON 和标准状态码。不处理会导致前端解析失败或重试逻辑混乱。
- 在自定义中间件中覆盖
buildResponse()方法,返回response()->json()并设status(429) - 务必带上
Retry-After响应头(单位:秒),这是 HTTP/1.1 标准字段,多数 SDK 会自动等待后重试 - 示例返回体:
{"message":"Too many requests.","retry_after":60}—— 不要加多余字段,避免客户端解析歧义 - 别在控制器里 try/catch 429:限流是前置拦截,应在中间件层就完成响应构造










