本文目录导读:

- 基础层:Nginx 层面限流(最高效)
- 应用层:Token + 时间窗口(最常用)
- 更简单的方案:Redis 计数器(固定窗口)
- 签名验证 + 时间戳(防重放攻击)
- 行为验证:图形验证码 / 滑块验证
- 设备指纹(高级方案)
- 黑白名单 + 自动封禁
- 方案组合建议
- 补充注意事项
针对 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);
}
可以结合 fail2ban 或 openresty 做更灵活的封禁。
方案组合建议
| 场景 | 推荐方案 |
|---|---|
| 全局限流 | Nginx limit_req + Redis 计数器 |
| 登录/注册 | 滑动窗口 + 验证码 + 失败次数封禁 |
| 支付/订单 | 签名验证 + nonce + 防重放 |
| 高并发抢购 | 滑动窗口 + 队列削峰 + 令牌桶 |
| API 对外开放 | 签名 + AppKey + 调用额度控制 |
补充注意事项
- 响应码统一: 所有拒绝请求应返回
HTTP 429 Too Many Requests,方便前端处理。 - 错误信息提示: 不要暴露具体原因(如“你被限流了”),统一返回“请求过于频繁,请稍后重试”。
- 合理设置阈值: 根据业务正常用户的峰值 + 20%~30% 余量设置。
- 日志记录: 拦截的请求应该记录日志,便于复盘和优化。
- 降级策略: 当 Redis 或其他依赖不可用时,应该自动降级(如临时去掉限流),而不是导致整个系统不可用。
版权声明: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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播