PHP项目投票功能如何防刷防重复

wen PHP项目 29

本文目录导读:

PHP项目投票功能如何防刷防重复

  1. 基础防线:来源与身份校验
  2. 核心防线:唯一用户认证(业务层)
  3. 行为与频率控制:触发式防御
  4. 技术层:前后端防御(提高攻击门槛)
  5. 数据库与写入层:绝对防重复
  6. 综合策略建议(从低到高成本,根据项目重要性选用)
  7. 代码示例(PHP + Redis + MySQL)

为PHP项目的投票功能设计防刷机制,需要从前端、后端、业务逻辑三个层面综合防御,没有100%绝对安全的系统,但通过多层叠加可以极大提高攻击成本。

以下是防刷防重复的体系化方案:

基础防线:来源与身份校验

这是最基础的,但容易被忽视。

  • IP限制:记录用户IP,对同一IP在单位时间(如1小时)内的投票次数进行限制。
    • 缺点:用户可通过代理/VPN更换IP。
  • Cookie/本地存储验证:用户投票后,在浏览器写入Cookie或LocalStorage标记。
    • 缺点:用户可清除本地数据。
  • Session/Token校验:用户登录后,同一Session或Token在有效期内只能投一票。
    • 缺点:未登录用户无法限制(可改为强制登录)。

核心防线:唯一用户认证(业务层)

这是防刷的关键,需要根据业务场景选择合适的唯一标识

  • 强制用户登录:要求用户必须注册并登录后才能投票,后台关联user_id与投票记录。
    • 优点:识别度高,可追溯。
    • 缺点:降低用户体验。
  • 手机号验证:投票前需短信验证,每个手机号只能投一次。
    • 适用范围:重要投票(如选秀、评优)。
  • 设备指纹识别:通过JavaScript收集用户设备的特征(浏览器、分辨率、字体、显卡、Canvas指纹等),生成唯一的设备ID。
    • 优点:即使用户换IP、清Cookie,设备指纹也较难改变。
    • 推荐类库FingerprintJS
  • 第三方账号绑定:强制使用微信、QQ、微博等第三方登录,利用平台的唯一标识。

行为与频率控制:触发式防御

  • 单位时间频率限制(Rate Limit)
    • 使用Redis等中间件控制同一个IP、同一个用户ID在每分钟/每小时/每天的投票次数。
    • 示例:同一IP每分钟最多投10次,每天最多投100次。
  • 投票间隔限制:两次投票之间必须有一定的时间间隔(如至少3秒)。
  • 日志与异常行为分析
    • 记录投票时间、IP、User-Agent、Cookie、设备指纹。
    • 告警:短时间内投票数激增、同一个User-Agent下出现数百个不同IP、投票时间间隔完全一致(机器刷票特征)。

技术层:前后端防御(提高攻击门槛)

  • 验证码
    • 滑块验证码/图形验证码:每次投票都需要拖动或点击。
    • 智能验证码(如极验、腾讯云验证码):基于行为轨迹判断是否为真人,对用户干扰较小。
    • 注意:仅第一次或异常时弹出,不要每次弹出影响体验。
  • Token防重放
    • 每次返回投票页面时,生成一个一次性nonce(随机数)绑定到提交表单中,服务器端校验该nonce是否已被使用。
    • 示例
      // 生成
      $_SESSION['vote_token'] = bin2hex(random_bytes(32));
      // 校验
      if (hash_equals($_SESSION['vote_token'], $_POST['token'])) {
          unset($_SESSION['vote_token']); // 立即销毁
          // 处理投票
      }
  • 请求签名验证:前端将投票参数(item_iduser_idtimestamp等)加上密钥进行hash签名,后端验证签名是否合法且未过期(可防止恶意构造请求)。

数据库与写入层:绝对防重复

这是最后一道屏障,利用数据库的唯一约束来保证同一个人无法投两次同一选项。

  • 联合唯一索引
    • 创建数据表时,设置(user_id, option_id)(ip, option_id, date) 为联合唯一索引。
    • 写入时使用INSERT ... ON DUPLICATE KEY UPDATEINSERT IGNORE,如果插入失败,则表示已投过。
  • 悲观/乐观锁:在高并发场景下,防止投票计数超卖。
    UPDATE vote_options SET count = count + 1 WHERE id = ? AND count < max_vote_count;

综合策略建议(从低到高成本,根据项目重要性选用)

场景 推荐方案
简单活动/内部投票 IP限制 + Cookie + 简单图形验证码
常规社区投票 强制登录(Session) + 单位时间频率限制 + Token防重放
重要商业投票 手机号验证/设备指纹 + 滑块验证码 + 联合唯一索引 + 业务日志分析
高价值/严肃投票 所有以上方案 + 人工审核 + 投票前提交身份证后六位 + 区块链存证

代码示例(PHP + Redis + MySQL)

<?php
session_start();
// 1. 基础验证:是否已登录
if (!isset($_SESSION['user_id'])) {
    die("请先登录");
}
$userId = $_SESSION['user_id'];
$optionId = $_POST['option_id'] ?? 0;
$voteToken = $_POST['vote_token'] ?? '';
// 2. Token防重放 (session方式)
if (!hash_equals($_SESSION['vote_token'], $voteToken)) {
    die("请求非法或已过期");
}
unset($_SESSION['vote_token']); // 立即销毁
// 3. 频率限制 (Redis)
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$rateKey = "vote:rate:" . $userId . ":" . date('YmdH'); // 按小时限制
if ($redis->incr($rateKey) > 10) { // 每小时最多投10票
    die("您的投票次数已达上限");
}
$redis->expire($rateKey, 3600);
// 4. 时间间隔检测 (Redis)
$intervalKey = "vote:interval:" . $userId;
if ($redis->exists($intervalKey)) {
    $lastTime = $redis->get($intervalKey);
    if (time() - $lastTime < 5) { // 至少5秒间隔
        die("请勿频繁操作");
    }
}
$redis->set($intervalKey, time());
// 5. 写库 (利用联合唯一索引)
try {
    $pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
    $stmt = $pdo->prepare("INSERT INTO vote_log (user_id, option_id, created_at) VALUES (?, ?, NOW())");
    $stmt->execute([$userId, $optionId]);
    // 更新统计
    $pdo->exec("UPDATE vote_options SET votes = votes + 1 WHERE id = " . intval($optionId));
    echo "投票成功";
} catch (PDOException $e) {
    if ($e->getCode() == 23000) { // 重复插入错误
        die("您已经投过票了");
    }
    die("系统错误");
}

防刷没有银弹,建议分层防御,即使破解了Cookie(第一层),还有IP限制(第二层)、设备指纹(第三层)、验证码(第四层)、数据库唯一索引(最后一层),对于商业项目,强制手机号验证 + 设备指纹 + 频率限制是目前性价比较高的组合。

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