|
|
@@ -816,40 +816,113 @@ class AuthController extends PublicController
|
|
|
$postParams = Yii::$app->request->post();
|
|
|
$allParams = array_merge($getParams, $postParams);
|
|
|
$mobile = $allParams['mobile'] ?? '';
|
|
|
+ $captchaVerifyParam = $allParams['captchaVerifyParam'] ?? '';
|
|
|
|
|
|
if (!stringUtil::isMobile($mobile)) {
|
|
|
util::fail('请填写正确手机号');
|
|
|
}
|
|
|
|
|
|
- // 1. 安全检查:单手机号 60 秒防刷
|
|
|
+ // 1. 验证阿里云人机验证码(生产环境强制,本地环境若未传则容灾放行)
|
|
|
+ if (empty($captchaVerifyParam)) {
|
|
|
+ if (Yii::getAlias('@env') !== 'local') {
|
|
|
+ util::fail('请先完成人机验证');
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (!$this->verifyAliyunCaptcha($captchaVerifyParam)) {
|
|
|
+ util::fail('人机验证未通过,请重试');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 安全检查:单手机号 60 秒防刷
|
|
|
$lockKey = 'LOGIN_SMS_LOCK_' . $mobile;
|
|
|
if (Yii::$app->redis->executeCommand('GET', [$lockKey])) {
|
|
|
util::fail('发送过于频繁,请稍后再试');
|
|
|
}
|
|
|
|
|
|
- // 2. 安全检查:单手机号每日上限 5 条
|
|
|
+ // 3. 安全检查:单手机号每日上限 5 条
|
|
|
$dayKey = 'LOGIN_SMS_COUNT_' . date('Ymd') . '_' . $mobile;
|
|
|
$sendCount = (int)Yii::$app->redis->executeCommand('GET', [$dayKey]);
|
|
|
if ($sendCount >= 5) {
|
|
|
util::fail('该手机号今日获取验证码已达上限');
|
|
|
}
|
|
|
|
|
|
- // 3. 生成 6 位随机验证码并保存至 Redis(有效期 5 分钟)
|
|
|
+ // 4. 生成 6 位随机验证码并保存至 Redis(有效期 5 分钟)
|
|
|
$code = (string)rand(100000, 999999);
|
|
|
$codeCacheKey = 'LOGIN_SMS_CODE_' . $mobile;
|
|
|
Yii::$app->redis->executeCommand('SETEX', [$codeCacheKey, 300, $code]);
|
|
|
|
|
|
- // 4. 调用系统自带的限流发送组件 (内置了 IP 60秒锁和每日15条限制)
|
|
|
+ // 5. 调用系统自带的限流发送组件 (内置了 IP 60秒锁和每日15条限制)
|
|
|
$minute = 5;
|
|
|
sms::send($mobile . ',' . $code . ',' . $minute, '登录验证码:{$var},{$var}分钟内有效');
|
|
|
|
|
|
- // 5. 写入单手机号限制锁
|
|
|
+ // 6. 写入单手机号限制锁
|
|
|
Yii::$app->redis->executeCommand('SETEX', [$lockKey, 60, '1']);
|
|
|
Yii::$app->redis->executeCommand('SETEX', [$dayKey, 86400, $sendCount + 1]);
|
|
|
|
|
|
util::complete('验证码发送成功');
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 【用途】校验阿里云验证码2.0
|
|
|
+ * 【为什么】利用已安装的 alibabacloud/client 进行通用 RPC 调用,校验前端传来的验证参数
|
|
|
+ */
|
|
|
+ private function verifyAliyunCaptcha($captchaVerifyParam)
|
|
|
+ {
|
|
|
+ if (empty($captchaVerifyParam)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ $accessKeyId = Yii::$app->params['aliyun']['accessKeyId'] ?? Yii::$app->params['accessKeyId'] ?? '';
|
|
|
+ $accessKeySecret = Yii::$app->params['aliyun']['accessKeySecret'] ?? Yii::$app->params['accessKeySecret'] ?? '';
|
|
|
+ $sceneId = Yii::$app->params['aliyun']['captchaSceneId'] ?? Yii::$app->params['aliyunCaptchaSceneId'] ?? 'YOUR_SCENE_ID';
|
|
|
+
|
|
|
+ if (empty($accessKeyId) || empty($accessKeySecret)) {
|
|
|
+ Yii::error("阿里云验证码校验失败:未配置 AccessKey");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 初始化阿里云客户端
|
|
|
+ \AlibabaCloud\Client\AlibabaCloud::accessKeyClient($accessKeyId, $accessKeySecret)
|
|
|
+ ->regionId('cn-hangzhou')
|
|
|
+ ->asDefaultClient();
|
|
|
+
|
|
|
+ // 发起通用 RPC 请求调用 VerifyIntelligentCaptcha
|
|
|
+ $result = \AlibabaCloud\Client\AlibabaCloud::rpc()
|
|
|
+ ->product('Captcha')
|
|
|
+ ->version('2023-03-05')
|
|
|
+ ->action('VerifyIntelligentCaptcha')
|
|
|
+ ->method('POST')
|
|
|
+ ->scheme('https')
|
|
|
+ ->options([
|
|
|
+ 'query' => [
|
|
|
+ 'CaptchaVerifyParam' => $captchaVerifyParam,
|
|
|
+ 'SceneId' => $sceneId,
|
|
|
+ ],
|
|
|
+ ])
|
|
|
+ ->request();
|
|
|
+
|
|
|
+ $data = $result->toArray();
|
|
|
+
|
|
|
+ Yii::info("阿里云验证码返回结果: " . json_encode($data), 'captcha');
|
|
|
+
|
|
|
+ // 兼容可能包裹在 Result 节点下或扁平结构
|
|
|
+ $verifyResult = $data['Result']['VerifyResult'] ?? $data['VerifyResult'] ?? null;
|
|
|
+ if ($verifyResult === true) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Yii::error("阿里云验证码校验异常: " . $e->getMessage());
|
|
|
+ // 容灾策略:如果是本地环境,可以放行
|
|
|
+ if (Yii::getAlias('@env') === 'local') {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 【用途】短信验证码登录
|
|
|
* 【为什么】提供免密码快捷登录方式,验证通过后自动签发 JWT Token 并返回商家后台所需的所有初始化数据
|