You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何防止内部统计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,使用后立即失效:

  1. 前端在触发交互前,向后端请求一个一次性Token;
  2. 发送统计请求时携带该Token;
  3. 后端验证Token有效性,处理请求后标记为已使用;
  4. 重复请求携带已失效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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 07:55:37