限流只是防刷的最外层,单靠它挡不住换 IP、复用登录态的黑产脚本。有效的防护是五层纵深:边缘层用 CDN 与 WAF 把大流量在回源前掐断;身份层强制鉴权,让每个请求可归因到具体账号或 App Key;协议层用签名加时间戳加 Nonce 阻断篡改与重放;人机层用无感验证码与设备指纹把脚本流量转成高成本人力操作;风控层按账号、设备、Cookie 等业务维度做多维频次统计与行为建模,动态封禁。限流负责保命,签名与风控负责识别,缺任一层都会被绕过。
一、先分清刷量类型,再配防护手段
“刷量”在不同业务里指向完全不同的攻击。阿里云 WAF 的账户安全模块把与账号相关的风险归为撞库、暴力破解、垃圾注册、弱口令嗅探、短信验证码接口滥刷五类。这几类的成本结构和防护重点各不相同:
| 刷量类型 | 攻击目标 | 典型特征 | 首选防护 |
|---|---|---|---|
| CC 洪水 | 打满带宽与连接数 | 持续高并发,单接口重复 | CDN + WAF + IP 频次 |
| 短信验证码滥刷 | 消耗短信费用 | 手机号高度分散,请求匀速 | 图形前置验证 + 手机号维度频控 |
| 撞库与暴力破解 | 盗取账号 | 登录失败率异常高 | 失败计数 + 强制验证码 + 账号锁定 |
| 数据爬取 | 抓价格、库存、内容 | 设备指纹高度统一,无跳转路径 | Bot 管理 + 签名校验 |
| 营销活动作弊 | 刷券、刷红包、刷任务 | 新注册账号集中,行为路径极短 | 设备指纹去重 + 业务风控规则 |
分类的意义在于选型。给短信接口加 IP 限流几乎无效,黑产用代理池即可绕开;给它加”同一手机号 1 分钟 1 次、同一设备 1 天 10 次”的业务维度频控才有效。
二、攻击流量的四维画像
识别先于拦截。腾讯云的实战总结给出四个可直接落库统计的判别维度:
| 维度 | 正常用户 | 攻击流量 |
|---|---|---|
| 请求频率 | 有明显高低峰波动 | 持续稳定高并发 |
| 时间分布 | 符合作息规律 | 24 小时均匀请求 |
| 设备多样性 | 设备类型与 OS 版本分散 | 设备指纹高度统一 |
| 行为路径 | 多页面跳转,有前置动作 | 单接口重复调用 |
把这四项做成实时指标看板,比任何单点规则都管用——攻击开始的前几秒,”时间分布均匀”和”无前置页面”这两条通常先亮红灯。
三、身份层与协议层:让请求可归因、不可重放
所有接口必须携带合法凭证(API Key、JWT 或 OAuth 2.0),并按角色分级权限;敏感操作再叠加一次性令牌。匿名接口是黑产的首选入口,能改成登录后可见的,就别留在公开路径上。
协议层的签名校验按下面顺序落地:
- 服务端为每个客户端签发 AppKey 与 AppSecret,Secret 不下发到前端产物;
- 客户端按参数名字典序拼接待签串,附加
timestamp与随机nonce; - 用 HMAC-SHA256 计算签名,放入请求头;
- 服务端先比对时间戳,偏移超过 5 分钟直接拒绝;
- 用 Redis
SETNX登记 nonce,TTL 与时间窗对齐,登记失败即判定重放; - 常量时间比较签名,失败请求单独计数,同一 AppKey 连续失败达阈值则临时封禁。
import hmac, hashlib, time
def verify(headers, params, secret, redis, window=300):
ts, nonce, sig = headers["X-Ts"], headers["X-Nonce"], headers["X-Sign"]
if abs(int(time.time) - int(ts)) > window:
return "expired"
if not redis.set(f"nonce:{nonce}", 1, nx=True, ex=window):
return "replay"
raw = "&".join(f"{k}={v}" for k, v in sorted(params.items)) + f"|{ts}|{nonce}"
expect = hmac.new(secret.encode, raw.encode, hashlib.sha256).hexdigest
if not hmac.compare_digest(expect, sig):
redis.incr(f"badsign:{headers['X-AppKey']}")
return "bad_signature"
return "ok"
原生 App 不适合弹验证码,阿里云给出的替代方案是 SDK 签名:SDK 采集移动端硬件与环境信息,对请求签名并在 WAF 侧验签,只放行来自合法官方 App 的请求,脚本、自动化程序、模拟器发出的请求一律拦在回源前。
四、人机层:把机器成本变成人力成本
4.1 渐进式挑战
验证码是防护重点接口最简单有效的手段,接入通常只需少量业务代码改动。但普通图形验证码正被黑产工具持续攻破,对抗强度要求高时需换专业验证码服务。现代做法是渐进式挑战:低风险请求无感放行,风险评分升高才弹滑块或二次校验,把摩擦精准加在可疑流量上。
4.2 设备指纹与行为分析
设备指纹基于硬件特征、浏览器配置生成稳定标识,指纹突变或大量账号共用同一指纹都是强信号;采集时应聚焦技术标识,避免触碰个人数据。行为分析看的是交互形态:鼠标轨迹过于规整、输入速度超出人类范围、页面跳转过快,都是脚本的破绽。
4.3 专业 Bot 管理
成熟方案把行为分析、设备指纹、IP 信誉、挑战机制打包成 Bot 管理产品,用于区分真人与自动化程序,保护接口免于爬取与欺诈。自建的性价比通常不如直接采购,尤其在 IP 信誉库这一项上。
五、风控层:多维频次与动态封禁
单一维度频控最容易被绕过。IP 会被代理池打散,UA 可以随手伪造。阿里云的建议是把统计对象换成与业务逻辑绑定的字段:当攻击请求使用大量代理 IP 但复用同一登录态 Cookie(如 uid)时,基于 Cookie 设置限速,防护对象就从”IP”升级为”账号”;旗舰版 WAF 支持在频率设置中使用自定义 Cookie、Header、参数作为统计对象。
-- 多维频次统计:任一维度超阈值即拦截
-- KEYS: 各维度键(uid / device / ip / phone) ARGV: 阈值, 窗口秒数
local window = tonumber(ARGV[#ARGV])
for i = 1, #KEYS do
local limit = tonumber(ARGV[i])
local cur = redis.call("INCR", KEYS[i])
if cur == 1 then redis.call("EXPIRE", KEYS[i], window) end
if cur > limit then
redis.call("SETEX", "ban:" .. KEYS[i], 1800, 1) -- 临时封禁 30 分钟
return {0, KEYS[i], cur}
end
end
return {1, "", 0}
封禁策略优先用”临时”而非”永久”。短时高频请求自动进黑名单、30 分钟后自动释放,既压住攻击又给误伤留了出路。行为分析可进一步基于请求时序、参数分布动态调整封禁力度。
六、边缘层与成本账
在应用层拦截攻击,服务器资源已经被消耗掉了。腾讯云给出一笔账:100 万 QPS 持续一小时是 36 亿次请求,按 API 网关 0.015 美元/万次计费,仅网关费用就是每小时 5400 美元。所以顺序必须是先边缘、后业务——在 CDN 层阻断大头流量,用验证码过滤剩余机器流量,把攻击 IP 列表下沉到防火墙。
# 回源前的第一道闸门
limit_req_zone $binary_remote_addr zone=api_per_ip:10m rate=100r/s;
location /critical_api {
limit_req zone=api_per_ip burst=50 nodelay;
proxy_pass http://backend;
}
同一篇实战总结记录了一个反面案例:某电商未做业务层限流,遭 80 万 QPS 攻击时 Nginx 扛住了流量,但下游订单服务的数据库连接池被打空,正常交易瘫痪两小时。入口不挂不等于系统安全,资源隔离、熔断降级、自动扩缩容要同步配齐。
七、分阶段落地路线
- 第一天:给所有公开接口配 IP 与账号双维度限流,开启云 WAF 基础规则,敏感接口挂验证码;
- 第一周:上线签名校验与 Nonce 防重放,敏感接口改为登录可见,补齐请求日志字段(IP、UA、设备号、账号);
- 第二周:接入设备指纹,按四维画像搭实时监控看板,配置阈值告警;
- 第三周:把频控统计对象从 IP 切到账号与设备,落地临时封禁与自动解封;
- 第一个月:核心与非核心服务拆分隔离,配置熔断降级与自动扩缩容;
- 长期:沉淀行为模型,用半监督学习识别异常模式并持续迭代规则。
八、三个容易踩的坑
只做入口限流不做资源隔离,攻击穿透到数据库连接池就前功尽弃。日志字段缺失让事后追查无从下手,设备号、AppKey、风控评分必须与请求日志同表落地。告警阈值设得过敏导致告警疲劳,应按严重级别分层告警,避免真实攻击被淹在噪声里。
常见问题(FAQ)
Q1:只做 IP 限流为什么不够?
黑产用代理池与 NAT 轻易绕过,须叠加账号、设备、Cookie 等业务维度频控。
Q2:短信验证码接口怎么防滥刷?
前置图形或无感验证,再按手机号、设备、IP 三维限次,并对失败率做告警。
Q3:签名校验能防住抓包破解吗?
不能完全防住。App 端需配合 SDK 环境检测与代码混淆,服务端仍要保留风控兜底。