防短信验证码被恶意轰炸,靠的不是某一道墙,而是一套叠加的频控:单手机号间隔限制、单手机号日上限、IP/设备维度限流、图形验证码前置,再加黑名单自动加黑。任何一层单独用都会被绕过——只限手机号,攻击者换号池;只限 IP,攻击者用代理池。下面给出可复用的实现与参数口径。
一、分层的短信风控思路
短信风控的目标不是”让短信少发”,而是让该发的稳定发出、不该发的在合适位置被拦下或二次验证。业内通常拆三层:
| 层级 | 手段 | 拦的是什么 |
|---|---|---|
| 成本门槛层 | 频控、图形验证码 | 最明显的异常批量请求 |
| 行为识别层 | 设备指纹、IP、黑名单 | 复杂机器流量与代理池 |
| 分级处置层 | 上行短信、扫脸、人机挑战 | 疑似风险,不简单误杀 |
只做拦截率会误伤正常用户,只做体验会让账单和账号体系暴露。三层之间要取平衡。
二、单手机号频次限制(保底机制)
单手机号发送频次限制是几乎所有厂商的保底手段:同一号码 60 秒内只能发一次,一天最多 10~20 条,按场景码分别计数。
用 Redis 的 setIfAbsent 而非”先 get 再 set”,能避免短信通道慢响应期间用户连点造成重复发送。
public void sendCode(String phone, String type) {
String codeKey = "sms:code:" + type + ":" + phone;
// 60 秒内已发过则直接拒绝,避免重复下发
Boolean ok = redis.opsForValue
.setIfAbsent(codeKey, "1", Duration.ofSeconds(60));
if (Boolean.FALSE.equals(ok)) {
throw new BizException("请勿频繁发送");
}
// 日上限:INCR 后判断,过期时间对齐自然日
String dayKey = "sms:day:" + type + ":" + phone;
Long count = redis.opsForValue.increment(dayKey);
if (count == 1) redis.expire(dayKey, untilMidnight);
if (count > 20) {
redis.delete(codeKey);
throw new BizException("今日发送次数已超限");
}
doSendSms(phone, genCode);
}
这套逻辑清晰、改动小,但有两个短板:攻击者用随机号池就完全失效;每次请求都打 Redis,高频时吃连接。所以它只是保底,不是全部。
三、IP 与设备维度限流
随机号池攻击下,手机号频控会失效,要靠 IP 限流 + 黑名单顶上。网关层对单 IP 做计数,超限返回 429;触发限流次数过多的 IP 自动加黑,不必事前配置。
3.1 网关层 Redis + Lua 原子计数
把”读取—判断—写入”放进 Lua,保证集群下计数准确。
-- 单 IP 滑动窗口计数
local key = KEYS[1] -- rate:ip:1.2.3.4
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2]) -- 窗口毫秒,如 1000
local limit = tonumber(ARGV[3]) -- 窗口内最大请求数
redis.call("ZREMRANGEBYSCORE", key, 0, now - window)
local cnt = redis.call("ZCARD", key)
if cnt >= limit then
return 0
end
redis.call("ZADD", key, now, now)
redis.call("PEXPIRE", key, window)
return 1
3.2 自动加黑流程
- 每条请求先在 Redis 记 IP 触发限流的次数;
- 触发次数超过阈值(如 1 秒内连超 100 次),把该 IP 写入黑名单集合;
- 黑名单命中直接返回 403,不再路由到业务服务;
- 黑名单设过期时间(如 1 天),避免误杀后长期不可恢复。
四、图形验证码前置
发送短信前先过图形/滑块验证码,能挡住多数自动化脚本,是成本门槛层的关键一环。注意三点:验证码必须在服务端生成与校验;单次有效,用过即废;校验失败不暴露”手机号是否存在”的时序差异。
不过图形验证码对抗 OCR 已显吃力,且获取验证码的接口自身也可能被刷爆 CPU,所以它要配合频控,而不是替代频控。
五、自然日计数与滚动计数
频控口径常被忽略:按”今天 0~24 点最多 10 次”是自然日,按”过去 24 小时最多 10 次”是滚动计数。攻击者可在 23:58 打满额度、0:01 清零再刷一轮,绕开自然日边界。短信风控优先用滚动计数,尤其针对 IP 和设备维度。
六、请求真实性与唯一性
伪造多 IP、多手机号时,用服务端下发的 Token 做唯一性标识:前端拿 Token 再请求发送接口,后端校验 Token 合法性,缺失或伪造直接拒绝。配合请求签名、时效性校验(时间戳窗口内有效),可进一步抬高伪造成本。
常见问题(FAQ)
Q1:只做接口限流行不行?
不行。限流会把正常用户也挡在门外,必须叠加手机号与 IP 频控。
Q2:图形验证码能单独防轰炸吗?
不能。OCR 可破,且取码接口自身会被刷,需与频控配合。
Q3:自然日和滚动计数怎么选?
短信风控优先滚动计数,避免攻击者利用自然日边界清零绕过。