前两天,粉丝群里的阿强(老倒霉蛋了)半夜给我发私信,说他们公司刚上线的一个 H5 活动页,半夜被SMS Boom(短信轰炸机)盯上了。 早上老板醒来一看阿里云账单,好家伙,一晚上干出去 20 多万条短信,直接损失了好几万现金。 老板脸都绿了,阿强当天就被 HR 约谈“优化”了。
去面试下家时,面试官又刚好问到:“在这个场景下,如果让你设计短信验证码接口,你怎么做防刷?”阿强只答了“前端倒计时”和“IP 限流”,面试官轻蔑一笑:“就这?黑产的秒拨 IP 池有几百万个,你防得住谁?”
兄弟们,短信防刷绝不仅仅是一个简单的 RateLimit 问题,它是一场与黑产的博弈。今天 Fox 就带大家撕开黑产的底裤,看看他们是怎么作案的,以及我们该如何用代码构建一套“铜墙铁壁”。
一、 认清对手:黑产的手段比你想象的更野
在防守之前,你得知道对手手里拿着什么武器。黑产刷短信接口,通常就两招:
短信轰炸(SMS Boom):
原理:黑客写个脚本,收集了成千上万个像你这样“裸奔”的接口。只要输入受害者的手机号,你的接口就成了黑客手里的“子弹”,疯狂给受害者发短信。
痛点:这种攻击不求利益,只为搞破坏。你的接口调用量会激增,但目标手机号非常集中。
薅羊毛(注册机):
原理:利用“接码平台”的廉价手机卡,配合自动化脚本批量注册账号,领新人红包。
痛点:这种攻击最难防。因为他们用的是真实手机号,IP 也是动态代理(秒级切换),你的普通限流规则瞬间失效。
二、 青铜防御:那些“骗自己”的手段
很多初级开发(比如阿强)喜欢在前端做文章:
做法:点击发送后,按钮变灰,前端倒计时 60 秒。
Fox 辣评:这就好比你家装了防盗门,结果窗户大开着。黑产是直接通过 HTTP 请求调你的后端接口,谁会傻傻地用浏览器去点你的按钮?前端防君子不防小人,所有防御必须下沉到服务端。
三、 黄金防御:核心代码落地(硬核实战)
既然简单的 IP 限流防不住代理池,那我们就得在上层逻辑上下功夫。以下这三道防线,缺一不可。
第一道防线:强制图形/滑块验证(后端二次校验)
这是拦截脚本最有效的手段。切记:不要只在前端校验滑块!我见过太多项目,前端滑块通过后,直接调短信接口,后端居然不校验滑块的 Token!黑客直接绕过滑块调接口,滑块成了摆设。
正确流程(Java 代码示例):
@RestController @RequestMapping("/sms") publicclass SmsController { @Autowired private CaptchaService captchaService; // 假设对接了极验或阿里云 @PostMapping("/send") public Result sendSms(@RequestBody SmsRequest req) { // 1. 第一步:必须先校验滑块验证码的 Ticket // 如果 Ticket 无效或已过期,直接抛异常,根本不进发短信逻辑 boolean isHuman = captchaService.verify(req.getCaptchaTicket(), req.getIp()); if (!isHuman) { return Result.error("验证失效,请重新滑动"); } // 2. 第二步:执行发送逻辑... } }原理:验证码服务商(如阿里云)会返回一个加密的 Ticket,后端拿着这个 Ticket 去服务商那边再查一次。只有服务商告诉你“这是个活人”,你才发短信。
第二道防线:基于 Redis 的多维限流(Lua 脚本原子性)
别只限 IP!IP 是最廉价的资源。要限制的是手机号和整体频次。 我们需要一个原子性的限流器。
Redis Lua 脚本(rate_limit.lua):
-- keys[1]: 限流 Key (例如 sms:limit:13800138000) -- argv[1]: 限流阈值 (例如 5 次) -- argv[2]: 过期时间 (例如 3600 秒) local current = redis.call('INCR', KEYS[1]) iftonumber(current) == 1then redis.call('EXPIRE', KEYS[1], ARGV[2]) end iftonumber(current) > tonumber(ARGV[1]) then return0-- 超过阈值 else return1-- 允许通过 endJava 调用代码:
@Autowired private StringRedisTemplate redisTemplate; public void checkRateLimit(String phone) { // 1. 限制单个手机号:1小时内只能发5条 (防轰炸) String phoneKey = "sms:limit:phone:" + phone; if (!executeLua(phoneKey, 5, 3600)) { thrownew BusinessException("操作太频繁,请稍后再试"); } // 2. 限制单个 IP:24小时内只能发 20 条 (防羊毛党,虽然IP可变,但能拦一部分是一部分) String ipKey = "sms:limit:ip:" + getClientIp(); if (!executeLua(ipKey, 20, 86400)) { thrownew BusinessException("当前 IP 请求受限"); } }第三道防线:接口参数签名(防止抓包重放)
黑产有时候会录制一个正常的请求包(包含有效的滑块 Ticket),然后疯狂重放。 为了防止这个,必须引入Sign 签名机制,并配合Timestamp和Nonce。
Java 校验逻辑:
public void verifySign(SmsRequest req) { // 1. 校验时间戳:防止 60 秒之前的请求被重放 long now = System.currentTimeMillis(); if (now - req.getTimestamp() > 60000) { thrownew BusinessException("请求已过期"); } // 2. 校验随机数 Nonce:防止 60 秒内的高频重放 // 将 nonce 存入 Redis,有效期 60 秒。如果 Redis 里已有该 nonce,说明是重放请求 String nonceKey = "sms:nonce:" + req.getNonce(); Boolean isAbsent = redisTemplate.opsForValue().setIfAbsent(nonceKey, "1", 60, TimeUnit.SECONDS); if (Boolean.FALSE.equals(isAbsent)) { thrownew BusinessException("重复的请求"); } // 3. 校验签名 Sign // 算法:MD5(phone + timestamp + nonce + secretKey) String raw = req.getPhone() + req.getTimestamp() + req.getNonce() + "MySecretKey"; String calcSign = DigestUtils.md5DigestAsHex(raw.getBytes()); if (!calcSign.equals(req.getSign())) { thrownew BusinessException("签名错误"); } }四、 王者防御:业务逻辑里的“骚操作”
如果上面的技术防线都被攻破了(比如黑产用了真人代刷平台),这时候就要靠业务逻辑来恶心他们了。
1. 场景化拦截(最重要!)
千万不要让短信接口是一个通用的发送器!
找回密码场景:用户输入手机号点发送。后端先查 DB,如果这个手机号根本没注册,直接报错!甚至可以返回“发送成功”但实际不发短信(逻辑伪装),防止黑产利用你的接口探测用户库,同时杜绝了给陌生号码发短信的可能。
注册场景:如果手机号已存在,直接提示“账号已存在,请登录”,坚决不发验证码。
2. 蜜罐参数(Honey Pot)
在前端页面里埋一个不可见的输入框。
<input type="text" name="robot_check" style="display:none;" />后端逻辑:如果接收到的请求里,robot_check字段有值,那 100% 是脚本干的!直接封禁该 IP,或者返回“发送成功”但拦截短信。
五、 兜底大招:Sentinel 网关流控
最后,不管你的代码写得再完美,都要留一手底牌——系统级熔断。 接入Sentinel或网关层限流,给短信接口配置一个总 QPS 阈值(比如 100/秒)。 就算防线全崩,至少你的短信余额不会在 1 分钟内归零。
# Sentinel 规则示例 resource: POST:/sms/send grade: QPS count: 100 # 只要超过 100 QPS,直接拒绝,保住钱包六、 总结与建议
兄弟们,短信防刷没有银弹,它是成本与体验的平衡。
滑块验证是性价比最高的方案,必须上,且后端必须校验。
Redis 限流要限制手机号和 IP 两个维度。
业务前置校验(查库)能拦截掉 50% 的无效攻击。
接口签名防止简单的抓包重放。
下次面试官再问你,把这套“滑块+RedisLua+签名+业务蜜罐+Sentinel兜底”的组合拳打出来,告诉他:“在我的架构里,想刷我的接口?得加钱找真人来刷!”
PS:如果你的业务只做国内,千万记得去阿里云/腾讯云后台,把‘国际/港澳台短信’的开关给关了!这一个开关,能帮你省下 90% 的潜在巨额损失。
https://mp.weixin.qq.com/s/x5g9rAhb2R91mkZJzSmF4g