必须加图形验证码且后端校验通过才允许调用短信接口,仅前端倒计时或IP限频无法防刷;需结合图形验证码校验、IP+手机号双维度Redis限频,并确保校验顺序与缓存清理正确。

必须加图形验证码,且后端必须校验通过才允许调用短信接口;仅靠前端倒计时或 IP 限频无法防刷。
为什么只做前端倒计时或 60 秒限制根本没用
前端 JS 倒计时(如 setInterval + 按钮禁用)纯属障眼法。攻击者可直接绕过页面,用 curl 或 Postman 批量发请求,mobile_sms_send_limit:138****1234 这类 Redis key 完全不生效。真实攻击中,工具会跳过所有前端逻辑,直击后端接口地址。
- 用户看到“60 秒后重试”,但服务端没做任何拦截,照样收请求、照样发短信
- 哪怕你加了
sleep(1)或简单时间戳比对,只要没绑定用户上下文(如 session 或图形验证码 token),就等于没设防 - ThinkPHP 的
input('post.phone')拿到手机号后,若没前置验证环节,下一步就是调第三方短信 SDK —— 这步必须卡死
ThinkPHP 中正确集成图形验证码的三步关键操作
不是“生成一个图放页面上”就完事。核心是:生成 → 提交 → 校验,三者必须闭环,且校验动作必须在短信发送逻辑之前执行。
- 控制器中用
think\captcha\facade\Captcha(TP6)或think\captcha\Captcha(TP5)生成图像,确保已开启Session中间件(TP6 需取消middleware.php中SessionInit注释) - 模板里用
{:captcha_img()}(TP6)或<img src="{:url('captcha')}">(TP5),并配 onclick 刷新:onclick="this.src='{:url('captcha')}?'+Math.random()" - 接收表单时,先调
captcha_check($data['vcode'])(TP6)或$captcha->check($data['captcha'])(TP5),返回false就立刻return json(['code'=>0, 'msg'=>'验证码错误']),绝不往后走
IP + 手机号双维度限频的 Redis 实现要点
图形验证码解决的是“是不是人”,限频解决的是“是不是恶意高频”。两者缺一不可,且限频 key 必须带业务标识,不能只按 IP 或只按手机号孤立统计。
立即学习“PHP免费学习笔记(深入)”;
- IP 限频 key 建议为
sms_ip_limit:{$_SERVER['REMOTE_ADDR']}:{date('Ymd')},单日最多 5 次,超限直接return json(['msg'=>'请求过于频繁']) - 手机号限频 key 建议为
sms_mobile_limit:{$mobile}:{date('Ymd')},同号 24 小时内最多 3 条,避免用户误操作被锁死 - 两个限频检查都必须放在图形验证码校验之后——否则攻击者可用无效验证码反复试探限频阈值
- 注意 Redis 连接必须配置正确,TP5 需在
config/cache.php中启用redis驱动,并确认host和port可达
容易被忽略的三个实际坑点
上线后才发现问题,往往卡在这几个地方:
-
captcha_check()返回true仅表示“格式对”,不代表“未过期”。ThinkPHP 默认有效期是 300 秒,但如果你改过expire配置却忘了同步前端刷新逻辑,用户填的可能是过期码 - 小程序或 App 调用接口时,
$_SERVER['REMOTE_ADDR']可能是 Nginx 或 CDN 的 IP,不是真实用户 IP。要用$_SERVER['HTTP_X_FORWARDED_FOR']并做白名单过滤,否则限频失效 - 短信接口返回成功后,没清空该手机号对应的验证码缓存(如
session_destroy()或Cache::pull('sms_code_'.$mobile)),会导致同一验证码被重复提交多次



















