Yii2如何仅在登录失败超3次时要求验证码校验?
问题分析与修复方案
首先,你的验证码校验逻辑不生效主要有几个关键问题,咱们一个个拆解:
1. checkattempts() 的返回值完全搞反了
Yii的验证规则中,when 回调的作用是:当返回 true 时,才会启用这条验证规则。你的需求是「登录失败次数≥3时要求验证码」,但现在的代码里:
if($data["attempts"] >=3){ return false; // 这里错了!次数够了反而不启用验证码 }else{ return true; // 次数不够却启用验证码 }
这直接导致逻辑完全颠倒,次数超过3次时反而跳过了验证码校验。
2. 未处理 $data 为 null 的情况
当用户第一次尝试登录(还没有任何失败记录)时,(new Query())->one() 会返回 null,这时候你直接访问 $data["attempts"] 会抛出「Trying to access array offset on null」的错误,导致整个函数执行失败,验证码规则自然无法正常触发。
3. getUser() 可能返回 null,导致调用 $user->id 报错
如果用户输入的用户名不存在,$this->getUser() 会返回 null,这时候在 checkattempts() 和 addLoginAttempt() 里调用 $user->id 会直接报错,后续逻辑都无法执行。
4. SQL语句存在注入风险
你直接把变量拼接到SQL字符串里,比如:
@Yii::$app->db->createCommand("UPDATE login_attempts SET attempts=".$attempts." where ip = '$ip' AND user_ref_id=$uid")->execute();
这种写法容易被SQL注入攻击,必须改成参数绑定的方式。
修正后的完整代码
先修正 checkattempts() 函数
public function checkattempts() { $user = $this->getUser(); $ip = $this->get_client_ip(); // 处理用户不存在的情况:按IP单独记录尝试次数 $query = (new Query())->select('attempts') ->from('login_attempts') ->where(['ip' => $ip]); if ($user) { $query->andWhere(['user_ref_id' => $user->id]); } else { // 用户不存在时,按IP统计尝试次数 $query->andWhere(['user_ref_id' => null]); } $data = $query->one(); $attempts = $data ? $data['attempts'] : 0; // 正确逻辑:次数≥3时返回true,启用验证码校验 return $attempts >= 3; }
修正 validatePassword() 函数
处理 $user 为 null 的情况,避免调用 $user->id 报错:
public function validatePassword($attribute, $params) { if (!$this->hasErrors()) { $user = $this->getUser(); if (!$user || !$user->validatePassword($this->password)) { // 当用户不存在时,传入null作为user_ref_id $this->addLoginAttempt($user ? $user->id : null); $this->addError($attribute, 'Incorrect username or password.'); } } }
修正 addLoginAttempt() 函数,改用参数绑定
public function addLoginAttempt($uid) { $ip = $this->get_client_ip(); $data = (new Query())->select('attempts') ->from('login_attempts') ->where(['ip' => $ip, 'user_ref_id' => $uid]) ->one(); if ($data) { // 使用参数绑定更新 Yii::$app->db->createCommand() ->update('login_attempts', [ 'attempts' => $data['attempts'] + 1 ], [ 'ip' => $ip, 'user_ref_id' => $uid ])->execute(); } else { // 使用参数绑定插入 Yii::$app->db->createCommand() ->insert('login_attempts', [ 'attempts' => 1, 'user_ref_id' => $uid, 'ip' => $ip ])->execute(); } }
最后修正验证规则里的 when 回调
确保回调里使用 $model 而不是 $this(更符合Yii的规范):
public function rules() { return [ [['username', 'password'], 'required'], ['password', 'validatePassword'], ['verifyCode', 'captcha', 'when' => function($model) { return $model->checkattempts(); }], ]; }
这样调整后,就能实现「登录失败次数超过3次时要求输入验证码」的需求了,同时也修复了潜在的报错和安全问题。
内容的提问来源于stack exchange,提问作者rji rji
相关产品推荐
相关产品推荐

