如何防止内部统计API被用户重复调用?(PHP、Laravel)
前端内部统计API的防护与重复请求拦截方案
一、基础防护机制(覆盖登录用户与访客)
1. 身份与权限校验
- 登录用户:依赖Laravel自带的
auth中间件,通过Session或JWT验证身份,确保只有合法用户能调用API。 - 访客:生成唯一匿名ID(如UUID)并存储在Cookie中,通过自定义中间件识别访客身份,避免完全无校验的API被批量恶意调用。
2. 请求来源校验
在自定义中间件中校验Referer头,仅允许来自自身域名的请求,拦截跨站的恶意调用:
// 示例中间件片段 if (!str_starts_with($request->header('Referer'), config('app.url'))) { return response()->json(['error' => '非法请求来源'], 403); }
3. 请求频率限制
利用Laravel的throttle中间件,针对登录用户(按用户ID)和访客(按匿名ID/IP)设置调用频率上限,防止批量请求消耗资源:
// 登录用户路由示例 Route::post('/stats/track', [StatsController::class, 'track']) ->middleware(['auth', 'throttle:20,1']); // 1分钟最多20次 // 访客路由示例(需配合匿名ID中间件) Route::post('/stats/track', [StatsController::class, 'track']) ->middleware(['assign.guest', 'throttle:10,1']);
4. 数据合法性校验
通过Laravel的FormRequest严格校验请求参数(如统计事件类型、关联资源ID格式),拒绝无效请求:
// 示例FormRequest public function rules() { return [ 'event_type' => 'required|string|in:click,view,submit', 'resource_id' => 'nullable|integer|exists:resources,id' ]; }
二、阻止重复请求的核心方案
通用思路:幂等性Token机制
这是解决浏览器重发请求最可靠的后端方案,核心逻辑是为每个合法请求分配唯一Token,使用后立即失效:
- 前端在触发交互前,向后端请求一个一次性Token;
- 发送统计请求时携带该Token;
- 后端验证Token有效性,处理请求后标记为已使用;
- 重复请求携带已失效Token时直接拒绝。
Laravel具体实现(幂等Token)
1. 生成Token接口
创建接口供前端获取唯一Token:
Route::get('/stats/token', function () { $token = Str::random(32); // Redis存储Token,有效期15分钟 Redis::setex("stats_token:{$token}", 900, 'unused'); return response()->json(['token' => $token]); });
2. 自定义幂等校验中间件
生成中间件并实现Token校验逻辑:
php artisan make:middleware CheckIdempotencyToken
// app/Http/Middleware/CheckIdempotencyToken.php namespace App\Http\Middleware; use Closure; use Illuminate\Support\Facades\Redis; class CheckIdempotencyToken { public function handle($request, Closure $next) { $token = $request->header('X-Idempotency-Token') ?? $request->input('idempotency_token'); if (!$token) { return response()->json(['error' => '缺少幂等Token'], 400); } $key = "stats_token:{$token}"; $status = Redis::get($key); if (!$status || $status === 'used') { return response()->json(['error' => '重复请求或Token已失效'], 409); } // 原子标记Token为已使用,避免并发问题 Redis::set($key, 'used'); return $next($request); } }
在App\Http\Kernel.php中注册中间件:
protected $routeMiddleware = [ // ...其他中间件 'idempotency' => \App\Http\Middleware\CheckIdempotencyToken::class, ];
3. 绑定中间件到统计API
Route::post('/stats/track', [StatsController::class, 'track']) ->middleware(['idempotency']); // 可结合auth/访客中间件
4. 前端配合实现
// 页面加载时获取Token let idempotencyToken; fetch('/stats/token') .then(res => res.json()) .then(data => idempotencyToken = data.token); // 触发统计请求 function trackEvent(eventType) { if (!idempotencyToken) return; fetch('/stats/track', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Idempotency-Token': idempotencyToken }, body: JSON.stringify({ event_type: eventType }) }) .then(res => { // 请求成功后重新获取新Token,用于下一次统计 return fetch('/stats/token'); }) .then(res => res.json()) .then(data => idempotencyToken = data.token) .catch(err => { // Token失效时重新获取 if (err.response?.status === 409) { fetch('/stats/token').then(res => res.json()).then(data => idempotencyToken = data.token); } }); }
三、额外优化建议
- 原子操作保障:使用Redis的Lua脚本或
SETNX命令,确保Token标记为已使用的操作是原子性的,避免并发请求导致重复处理; - 前端辅助拦截:在发送请求后禁用触发按钮或标记交互状态,减少前端误操作导致的重复请求;
- 日志监控:记录重复请求日志,监控API调用频率,及时发现异常行为。
内容的提问来源于stack exchange,提问作者Vlad K
相关产品推荐
相关产品推荐

