You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:32:27