限流必须分层做:ip、手机号、设备token三道关卡缺一不可;签名需含appkey、timestamp、nonce、mobile、template_id五要素并hmac-sha256加密;滑动验证码token须绑定mobile与timestamp且一次有效;redis操作须用nx、带版本前缀、避免del误删。

限流必须分层做:IP、手机号、设备Token三道关卡缺一不可
只靠单一维度限流,比如只限制IP频率,根本挡不住分布式脚本或众包人工攻击;只限制手机号,又防不住“A号请求、B号收码”的盗刷漏洞。真实攻防中,攻击者会同时绕过多个单点防护。
实操建议:
-
IP级限流放在网关层(如Nginx),用limit_req配置burst和nodelay,阈值设为60r/m(每分钟60次)足够压制扫描器,但别设成1r/s——否则用户切WiFi重连就触发限流 -
手机号级限流必须走Redis原子操作:INCR+EXPIRE组合,例如INCR sms:limit:138****1234并设置EXPIRE 3600(1小时窗口)。注意:不能先查再增,否则并发下会超发 -
设备Token级限流依赖前端注入的X-Device-ID或device_fingerprint,服务端校验该Token是否已存在且未被标记为异常。没Token?直接拒绝,不给验证码机会
签名验证别只校时间戳:nonce+appsecret+动态盐值才是底线
只校timestamp±5分钟,等于把门锁换成插销——重放攻击随便进。攻击者截一个合法请求,改个手机号就能刷穿接口。
实操建议:
- 签名字符串必须包含
appkey、timestamp、nonce、mobile(手机号)、template_id(模板ID),五者拼接后用HMAC-SHA256而非MD5计算,避免碰撞风险 -
nonce必须服务端生成并缓存(如Redis里存nonce:abc123 → used,TTL=300秒),用完即焚。客户端传重复nonce?直接401 - 额外加一层动态盐值:从数据库查出该
appkey对应的app_secret后,拼上当天日期(如20260210)再哈希,防止密钥泄露后长期有效
验证码前置不是加个图片就完事:滑动/点选必须绑定业务上下文
纯静态图片验证码早被打码平台批量破解,识别率超92%;更糟的是,如果滑动验证的token和后续短信请求完全解耦,攻击者拿到验证成功响应后,可无限复用该token调用短信接口。
实操建议:
- 滑动验证返回的
captcha_token必须绑定本次会话的mobile和timestamp,服务端存为captcha:abc123 → {"mobile":"138****1234","ts":1739153349,"used":false} - 调用
/sms/send时,必须携带该captcha_token,且服务端要校验:used == false、ts在5分钟内、mobile与token中一致——三者缺一不可 - 验证通过后立即将
used设为true,哪怕短信发送失败也不能二次使用。别信“前端控制不点击就没问题”,所有校验必须服务端强制执行
Redis缓存设计最容易翻车:key命名、过期、原子性三处常漏检
短信验证码逻辑高度依赖Redis,但SET mobile:138****1234 123456 EX 300这种写法看着简单,线上极易出问题:缓存击穿、并发覆盖、误删其他key……
实操建议:
- key必须带业务前缀和版本号,如
sms:code:v2:138****1234,避免不同环境或重构后key冲突 - 绝不单独用
SET,改用SET sms:code:v2:138****1234 123456 EX 300 NX(NX确保仅当key不存在时才设),防止并发下覆盖验证码 - 不要依赖
DEL清理,用EXPIRE设定固定过期时间;若需提前失效(如用户换号),用GETSET写空值+新过期时间,避免DEL误删正在使用的key










