在yii2.0.51中配置短信验证码需四步:一配阿里云sms组件(禁用主账号ak、签名模板须审核);二建aliyunsms服务类(校验手机号、调用sdk发送);三生成缓存验证码(redis setex设300秒过期);四验证并限流(redis比对+删除防重放、每小时限1次发送)。

在 Yii2.0.51 中完成短信验证码发送配置,必须确保请求身份可验、验证码可存、时效可控、调用可限,否则用户可能收不到码、重复发码、被恶意刷码,甚至暴露 AccessKey 导致账户被盗用。
配置阿里云短信服务基础参数
打开 config/web.php,在 components 下新增 sms 组件:
'sms' => [ 'class' => 'app\components\AliyunSms', 'accessKeyId' => 'LTAI5tQZzXxXxXxXxXxXxXxX', 'accessKeySecret' => 'ZxXxXxXxXxXxXxXxXxXxXxXxXx', 'regionId' => 'cn-hangzhou', 'signName' => '你的已审核签名', 'templateCode' => 'SMS_123456789', ]
注意:AccessKey 必须从阿里云控制台的 【AccessKey 管理】→【创建子用户并授权 AliyunSMSFullAccess】 获取,严禁使用主账号 AK;【signName 和 templateCode 必须已在阿里云短信服务后台通过审核】,否则发送必定失败且无明确错误提示。
实现短信发送服务类
在 app/components/AliyunSms.php 中创建服务类:
继承 \yii\base\Component,注入 aliyun-openapi-php-sdk(需通过 composer 安装 aliyun-openapi-php-sdk 或使用官方 alibabacloud/pop-core)。
定义 realSend($phone, $code, $usage) 方法:先校验手机号格式(^1[3-9]\d{9}$),再拼接模板参数 ['code' => $code],最后调用 SendSmsRequest 发送。失败时抛出异常,不静默吞掉错误。
这一步操作起来很简单,直接把文件拖进去就行,但若漏掉 use AlibabaCloud\SDK\Core\Profile\DefaultProfile; 会导致类找不到,报错信息模糊难排查。
生成并缓存验证码
第一步:在控制器中生成 6 位纯数字验证码:$code = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
第二步:将验证码与过期时间(如 300 秒)一并写入 Redis:$redis->setex('sms:' . $phone, 300, json_encode(['code' => $code, 'ts' => time()]));
第三步:调用 Yii::$app->sms->realSend($phone, $code, 'login'); 发送。
必须用 【Redis 的 setex 命令而非普通 set】,否则无法自动过期;若用 MySQL 存储,务必加索引到 phone 和 create_time 字段,否则高并发下查询变慢,用户等不到验证码。
验证用户提交的验证码
方法一:直接读取 Redis 缓存比对
$cacheKey = 'sms:' . $phone; $cached = $redis->get($cacheKey); if (!$cached) { throw new BadRequestHttpException('验证码已失效或未发送'); } $data = json_decode($cached, true); if ($data['code'] !== $inputCode || time() > $data['ts'] + 300) { throw new BadRequestHttpException('验证码错误或已过期'); } $redis->del($cacheKey); // 验证成功立即删除,防重放
方法二:使用自定义 Validator(耦合度更低)
创建 app/validators/SmscodeValidator.php,重写 validateAttribute(),内部调用 Redis 查询并校验时效。在 Model rules 中声明:['smsCode', SmscodeValidator::class, 'phoneAttribute' => 'mobile', 'expireTime' => 300]。
注意:验证通过后必须立刻 $redis->del(),否则同一验证码可多次使用,绕过安全验证。
添加防刷与限流逻辑
在发送验证码前插入频控检查:
用 Redis 记录手机号最近一次发送时间:$lastSend = $redis->get('sms_last_send:' . $phone); if ($lastSend && time() - $lastSend
再记录本次发送时间:$redis->setex('sms_last_send:' . $phone, 3600, time());
这一步不能省略——没有限流,攻击者可在 1 秒内发起数百次请求,迅速耗尽当日配额,真实用户全部无法接收验证码。











