php短信验证码需确保稳定生成、安全存储、精准发送与原子校验:用random_int生成6位码存redis(5分钟过期),严格匹配审核通过的签名/模板,templateparam须json字符串且键名与模板占位符一致,校验时原子读取并删除,同步ntp时间,记录失败次数防暴力。

PHP 短信接口接入验证码功能,核心不是“调通就行”,而是要确保 sendSms() 调用能稳定返回可校验的随机码、且用户收到及时、服务端能安全比对。多数失败发生在签名/模板审核未过、参数顺序错乱、或验证码未落库就去校验。
短信服务商选择与资质准备
国内必须用有《增值电信业务经营许可证》的厂商(如阿里云短信、腾讯云短信、容联云),否则发不出验证码。个人开发者不能直接对接三大运营商网关。
- 注册账号后,先实名认证,再申请「短信签名」和「短信模板」——模板内容必须含「验证码:{1}」且不可修改占位符格式
- 签名审核通常 1–2 个工作日,模板审核更快;测试阶段可用沙箱环境,但正式上线前必须用审核通过的签名+模板 ID
- 获取到的
accessKeyId、accessKeySecret、signName、templateCode是后续调用必需参数,别硬编码在 PHP 文件里
生成与存储验证码的正确姿势
验证码不是随便 rand(1000,9999) 就完事。它得可验证、有时效、防重放、且不暴露给前端。
- 用
random_int(100000, 999999)生成 6 位数字(避免mt_rand可预测) - 存入 Redis 更合适:
$redis->setex("sms:verify:{$phone}", 300, $code)(5 分钟过期,key 建议带前缀防冲突) - 绝对不要把验证码存在 session 或 MySQL 普通表里——前者跨请求丢失,后者无 TTL 易堆积
- 如果用数据库存,必须加
expire_at字段并配定时清理,否则用户反复请求会塞满表
调用短信 SDK 发送时的关键参数
以阿里云 SDK 为例,SendSmsRequest 的 TemplateParam 必须是 JSON 字符串,不是数组;且字段名必须和模板中占位符严格一致。
立即学习“PHP免费学习笔记(深入)”;
$param = json_encode(['code' => $code], JSON_UNESCAPED_UNICODE);
// ✅ 模板里写的是 {code},这里 key 就得是 'code'
// ❌ 不能写成 ['verify_code' => $code] 或 {'code': $code}(单引号无效)
-
PhoneNumbers参数只接受字符串,如"13800138000",不能带 +86、空格或横线 - 发送频率限制由服务商控制(如阿里云默认 1 条/分钟/号码),超限会返回
isSuccess=false和错误码isv.BUSINESS_LIMIT_CONTROL - 调试时打开 SDK 日志,捕获真实响应体,别只看 HTTP 状态码 200 —— 很多失败响应体里
Message是 "Template does not exist"
验证码校验环节最容易忽略的细节
用户提交验证码后,后端不是简单查 Redis 是否存在,而是要完成原子性校验+失效操作,否则可能被重放利用。
- 用
$redis->get("sms:verify:{$phone}")读值后,立刻$redis->del("sms:verify:{$phone}")—— 防止同一码多次使用 - 校验前先判断是否为空或过期,空则直接返回「验证码已失效」,别抛异常或 500
- 前后端时间需同步(NTP),Redis 过期依赖系统时间;若服务器时间慢 2 分钟,用户填对了也提示「验证码错误」
- 记录失败次数,连续 5 次输错就锁定该手机号 15 分钟(用另一个 Redis key 控制),防暴力枚举
真正卡住人的往往不是代码写不对,而是签名没审核过、模板变量名拼错一个字母、Redis 连接超时没设重试、或者验证码生成后没存成功却以为发出去了。每个环节都要有明确的日志输出和兜底反馈,而不是静默失败。











