本文目录导读:

- 基础防线:来源与身份校验
- 核心防线:唯一用户认证(业务层)
- 行为与频率控制:触发式防御
- 技术层:前后端防御(提高攻击门槛)
- 数据库与写入层:绝对防重复
- 综合策略建议(从低到高成本,根据项目重要性选用)
- 代码示例(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_id、user_id、timestamp等)加上密钥进行hash签名,后端验证签名是否合法且未过期(可防止恶意构造请求)。
数据库与写入层:绝对防重复
这是最后一道屏障,利用数据库的唯一约束来保证同一个人无法投两次同一选项。
- 联合唯一索引:
- 创建数据表时,设置
(user_id, option_id)或(ip, option_id, date)为联合唯一索引。 - 写入时使用
INSERT ... ON DUPLICATE KEY UPDATE或INSERT 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限制(第二层)、设备指纹(第三层)、验证码(第四层)、数据库唯一索引(最后一层),对于商业项目,强制手机号验证 + 设备指纹 + 频率限制是目前性价比较高的组合。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播