PHP项目接口防刷有哪些实现方案

wen PHP项目 25

本文目录导读:

PHP项目接口防刷有哪些实现方案

  1. 基础层:Nginx 层面限流(最高效)
  2. 应用层:Token + 时间窗口(最常用)
  3. 更简单的方案:Redis 计数器(固定窗口)
  4. 签名验证 + 时间戳(防重放攻击)
  5. 行为验证:图形验证码 / 滑块验证
  6. 设备指纹(高级方案)
  7. 黑白名单 + 自动封禁
  8. 方案组合建议
  9. 补充注意事项

针对 PHP 项目的接口防刷,核心目标是防止恶意高频请求脚本重复调用资源滥用,以下是多层次的实现方案,建议根据项目规模和精度组合使用。


基础层:Nginx 层面限流(最高效)

在进入 PHP 应用之前拦截,性能最好,适合全局防护。

限制单 IP 并发连接数:

limit_conn_zone $binary_remote_addr zone=addr:10m;
server {
    location /api/ {
        limit_conn addr 5; # 每个IP最多5个并发连接
        limit_conn_status 429;
    }
}

限制请求速率:

limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
server {
    location /api/ {
        limit_req zone=one burst=5 nodelay; # 每秒10个请求,突发允许5个
        limit_req_status 429;
    }
}

应用层:Token + 时间窗口(最常用)

在 PHP 内部实现,可按用户、IP、接口精细化控制。

方案:滑动窗口计数器(推荐)

使用 Redis 的 zset(有序集合)记录请求时间戳,时间窗口滑动淘汰过期数据。

class RateLimiter {
    private $redis;
    private $key;
    public function __construct($key, $maxRequests = 60, $window = 60) {
        $this->redis = new Redis();
        $this->redis->connect('127.0.0.1', 6379);
        $this->key = $key;
        $this->maxRequests = $maxRequests;
        $this->window = $window;
    }
    public function allow() {
        $now = microtime(true);
        $start = $now - $this->window;
        // 移除窗口外的旧记录
        $this->redis->zRemRangeByScore($this->key, 0, $start);
        $count = $this->redis->zCard($this->key);
        if ($count >= $this->maxRequests) {
            return false; // 拒绝
        }
        // 添加当前请求记录
        $this->redis->zAdd($this->key, $now, $now . rand(1000, 9999));
        // 设置过期时间,避免内存泄漏
        $this->redis->expire($this->key, $this->window + 10);
        return true;
    }
}
// 使用示例:
$limiter = new RateLimiter("user:123:api:login", 5, 60); // 60秒内5次
if (!$limiter->allow()) {
    http_response_code(429);
    die(json_encode(['error' => '请求太频繁']));
}

注意zAdd 时要确保唯一性(后面加随机数),避免重复覆盖。


更简单的方案:Redis 计数器(固定窗口)

适合轻量级场景,但会有“临界点”问题(窗口最后一秒突发)。

$key = "rate:{userId}:api:login";
$limit = 5;
$window = 60; // 秒
$current = $redis->get($key);
if ($current && $current >= $limit) {
    die("请求过于频繁");
}
$redis->multi();
$redis->incr($key);
$redis->expire($key, $window);
$redis->exec();

签名验证 + 时间戳(防重放攻击)

适合敏感接口(支付、登录),防止请求被截获后重复发送。

// 服务端验证
function validateRequest($params, $signature, $timestamp, $timeout = 300) {
    // 1. 检查时间戳是否过期
    if (abs(time() - $timestamp) > $timeout) {
        return false;
    }
    // 2. 检查签名是否被使用过(防重放)
    $nonceKey = "nonce:{$params['nonce']}";
    if ($redis->exists($nonceKey)) {
        return false;
    }
    $redis->setex($nonceKey, $timeout, 1);
    // 3. 计算签名并比对
    $expected = md5(ksort($params) . $secretKey . $timestamp);
    return $expected === $signature;
}

请求方逻辑:

$params = ['action' => 'pay', 'amount' => 100];
$params['nonce'] = uniqid();   // 随机数,保证唯一
$params['timestamp'] = time();
ksort($params);
$sign = md5(implode('', $params) . $secretKey);

行为验证:图形验证码 / 滑块验证

在 N 次失败后强制要求验证码,可以有效阻止脚本攻击。

if ($redis->get("fail:user:{$uid}") > 3) {
    // 前端需要传入验证码token
    if (!verifyCaptcha($_POST['captcha_token'])) {
        die(json_encode(['code' => 1001, 'msg' => '需要人机验证']));
    }
    $redis->del("fail:user:{$uid}");
}

设备指纹(高级方案)

通过收集客户端 UA、屏幕分辨率、Canvas、WebGL 等信息生成指纹,识别异常访问。

可在前端生成指纹传给后端,后端判断该指纹是否异常。


黑白名单 + 自动封禁

结合日志监控,对异常 IP/UserID 自动动态封禁。

function checkBanned($userId, $ip) {
    $banKey = "ban:user:{$userId}";
    $banIp = "ban:ip:{$ip}";
    if ($redis->exists($banKey) || $redis->exists($banIp)) {
        die("访问已被封禁");
    }
}
function banUser($userId, $duration = 3600) {
    $redis->setex("ban:user:{$userId}", $duration, 1);
}

可以结合 fail2banopenresty 做更灵活的封禁。


方案组合建议

场景 推荐方案
全局限流 Nginx limit_req + Redis 计数器
登录/注册 滑动窗口 + 验证码 + 失败次数封禁
支付/订单 签名验证 + nonce + 防重放
高并发抢购 滑动窗口 + 队列削峰 + 令牌桶
API 对外开放 签名 + AppKey + 调用额度控制

补充注意事项

  1. 响应码统一: 所有拒绝请求应返回 HTTP 429 Too Many Requests,方便前端处理。
  2. 错误信息提示: 不要暴露具体原因(如“你被限流了”),统一返回“请求过于频繁,请稍后重试”。
  3. 合理设置阈值: 根据业务正常用户的峰值 + 20%~30% 余量设置。
  4. 日志记录: 拦截的请求应该记录日志,便于复盘和优化。
  5. 降级策略: 当 Redis 或其他依赖不可用时,应该自动降级(如临时去掉限流),而不是导致整个系统不可用。

抱歉,评论功能暂时关闭!