阿江 2017-04-24 18:09:43 10563次浏览 8条回复 5 1 0

因Yii2验证码Captcha原校验方式有bug,总是报错:验证码不正确,导致无法实现Ajax验证,今天研究了一下Yii2的源代码,把这个问题解决掉了。

主要解决方法:

1)还是使用Yii2的yii\captcha\CaptchaAction,进行验证码的生成、显示等操作。 2)但使用自定义的codeVerify验证方法进行验证,避开Yii2的验证Bug 3)验证完成后要重新生成新的验证码。 有其他特殊需求可以在这些代码中自己添加即可。

源代码:

文件位置:D:\phpwork\news\models\RegisterForm.php

class RegisterForm extends Model{
    public function rules()
    {
        return [
            //替代['verifyCode', 'captcha'],
            ['verifyCode', 'codeVerify'],
			......
        ];
    }
	//使用自定义的codeVerify验证方法进行验证,避开Yii2的验证Bug
    public function codeVerify($attribute) {
        //参数:'captcha',即控制器中actions()内的名称'captcha';Yii::$app->controller,调用验证的当前控制器(必须设置)
        $captcha_validate  = new \yii\captcha\CaptchaAction('captcha',Yii::$app->controller);
        if($this->$attribute){
            $code = $captcha_validate->getVerifyCode();
            if($this->$attribute!=$code){
                $this->addError($attribute, 'The verification code is incorrect.');
            }
        }
    }

文件位置:D:\phpwork\news\controllers\SiteController.php

class SiteController extends CommonController{
    public function actions(){
        return [
            'captcha' => [
                'class' => 'yii\captcha\CaptchaAction',
            ],
        ];
    }
   public function actionRegister()
    {
        $model = new RegisterForm();
        if ($model->load(Yii::$app->request->post())) {
           if (Yii::$app->request->isAjax) {
                Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
                return \yii\widgets\ActiveForm::validate($model);
            }
            if($model->validate()){
                $error=$model->save();
                if($error) {
                    return $this->errorDisplay($error);
                }else{
					//数据存储成功后,要重新生成新的验证码,否则原验证码不会改变
                    $captcha_validate  = new \yii\captcha\CaptchaAction('captcha',$this);
                    $captcha_validate->getVerifyCode(true);
                    Yii::$app->session->setFlash('success');
                    return $this->refresh();
                }
            }else{
                return $this->errorDisplay($model->getErrors());
            }
        }else{
            return $this->render('register', [
                'model' => $model,
            ]);
        }
    }

文件位置:D:\phpwork\news\views\site\register.php

<?php
/* @var $this yii\web\View */
/* @var $form yii\bootstrap\ActiveForm */
/* @var $model app\models\ContactForm */
use yii\helpers\Html;
use yii\bootstrap\ActiveForm;
use yii\captcha\Captcha;
$this->title = 'User Register';
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="site-contact">
    <h1><?= Html::encode($this->title) ?></h1>
    <?php if (Yii::$app->session->hasFlash('success')): ?>
        <div class="alert alert-success">
            Register successful!
        </div>
        <p>
            You can now <a href="/site/login">Login</a>.
        </p>
    <?php else: ?>
        <div class="row">
            <div class="col-lg-6">
                <?php $form = ActiveForm::begin([
                    'id' => 'contact-form',
                ]); ?>
                    <?= $form->field($model, 'member',['enableAjaxValidation'=>true])->textInput(['autofocus' => true])->hint('Can be chinese,grapheme or number.Only use to login,not public display!') ?>
                    <?= $form->field($model, 'memkey')->passwordInput() ?>
                    <?= $form->field($model, 'memkey_repeat')->passwordInput()  ?>
                    <?= $form->field($model, 'nickname',['enableAjaxValidation'=>true])->textInput()->hint('Can be chinese,grapheme or number,can have blank space.For public display only!') ?>
                    <?= $form->field($model, 'verifyCode',['enableAjaxValidation'=>true])->widget(Captcha::className(), [
                        'captchaAction'=>'site/captcha',
                        'imageOptions'=>['id'=>'captchaimg','alt'=>'点击换图','title'=>'点击换图', 'style'=>'cursor:pointer'],
                        'template' => '<div class="row"><div class="col-lg-3">{image}</div><div class="col-lg-6">{input}</div></div>',
                    ]) ?>
                    <div class="form-group">
                        <?= Html::submitButton('Submit', ['class' => 'btn btn-primary', 'name' => 'contact-button']) ?>
                    </div>
                <?php ActiveForm::end(); ?>
            </div>
        </div>
    <?php endif; ?>
</div>

关于Yii2图形验证码Bug产生的原因

Yii2图形验证码Bug产生的原因:首次验证通过后会重新生成验证码,导致二次验证失败! 具体执行过程:方法validate($input, $caseSensitive)在Ajax进行第一次验证后,$valid为true,导致$this->getVerifyCode(true);被执行,即重新生成了新的VerifyCode,当$model->verify()进行第二次验证时,则必定失败(新的VerifyCode与刚才输入的已经不一样了!),以下是Yii2原码:

文件位置:D:\phpwork\news\vendor\yiisoft\yii2\captcha\CaptchaAction.php

    public function validate($input, $caseSensitive)
    {
        $code = $this->getVerifyCode();
        $valid = $caseSensitive ? ($input === $code) : strcasecmp($input, $code) === 0;
        $session = Yii::$app->getSession();
        $session->open();
        $name = $this->getSessionKey() . 'count';
        $session[$name] = $session[$name] + 1;
		//Bug由以下三行代码产生:
        if ($valid || $session[$name] > $this->testLimit && $this->testLimit > 0) {
            $this->getVerifyCode(true);
        }
        return $valid;
    }

(全文完)

觉得很赞
  • 回复于 2017-05-11 15:58 举报

    我也踩到这个坑了,谢up主分享经验

  • 回复于 2017-07-20 16:26 举报

    感谢楼主,特地登录回复,表示感谢! 最好转换成小写,不然还是验证失败。
    public function codeVerify($attribute) {

        $captcha_validate  = new \yii\captcha\CaptchaAction('captcha',Yii::$app->controller);
        if($this->$attribute){
            $code = $captcha_validate->getVerifyCode();
            $verify=$this->$attribute;
            if(strtolower($verify)==strtolower($code)){
                return true;
            }else{
                $this->addError($attribute, 'The verification code is incorrect.');
            }
        }
    } }
    }
    
    
    
  • 回复于 2017-08-30 22:03 举报

    继承yii的CaptchaAction,将这个validate方法重写一下,去掉验证成功重新获取验证码的那个条件,然后在你使用captcha的控制器中将captcha的class改为这个新建的CaptchaAction应该就可以了

    public function actions()
        {
            return [
                'captcha' => [
                    **'class' => 'common\actions\CaptchaAction',**
                    'minLength' => 4,
                    'maxLength' => 4,
                    'transparent' => true,
                    'offset' => 8,
                    'padding' => 1,
                ],
            ];
        }
    
    1 条回复
    回复于 2017-10-05 16:12 回复

    请问如何重写YII自带的类方法?可否指点下?

  • 回复于 2017-09-11 17:10 举报
    public function prevCaptcha($attribute, $params)
    {
        $captcha = $this->createCaptchaAction();
        $value = $this->vcode;
        $valid = !is_array($value) && $captcha->validate($value, $this->caseSensitive);
        if (!$valid) {
            $this->addError($attribute, ErrorCode::ERROR_VCODE_NOT_MATCH);
        }
    }
    public function createCaptchaAction()
    {
    
        $ca = Yii::$app->createController($this->captchaAction);
        return $this->captchaAction;
        if ($ca !== false) {
            /* @var $controller \yii\base\Controller */
            list($controller, $actionID) = $ca;
            $action = $controller->createAction($actionID);
            $action->regenerateAfterValidation = false;
            if ($action !== null) {
                return $action;
            }
        }
        throw new InvalidConfigException('Invalid CAPTCHA action ID: ' . $this->captchaAction);
    }
    

    上面是重写的验证方法,总在下面这一步报错,有什么解决办法吗
    $ca = Yii::$app->createController($this->captchaAction);
    $this->captchaAction = 'api/article/captcha';

    3 条回复
    回复于 2017-09-11 20:59 回复

    报的什么错?

    回复于 2017-09-12 07:38 回复

    在$ca = Yii::$app->createController($this->captchaAction);创建控制器的时候总返回false

    回复于 2017-09-18 13:14 回复

    $this->captchaAction,这个的值是什么?

    觉得很赞
  • 回复于 2017-09-18 13:13 举报

    $this->captchaAction,这个的值是什么?

  • 回复于 2018-03-31 20:55 举报

    还用去后台验证吗?客户端验证不就完事了吗?

    1 条回复
    回复于 2018-04-11 10:08 回复

    。。。客户端验证你要这验证码的意义何在

  • 回复于 2018-03-31 21:03 举报

    真要做这个ajax验证的话,你拿这个post过来的验证码和session里面的比较一下不就OK!

    Yii::$app->response->format =Response::FORMAT_JSON;
    return ActiveForm::validate($model,[...]);
    
  • 回复于 2020-07-05 14:28 举报

    3Qs 楼主

您需要登录后才可以回复。登录 | 立即注册