Fork me on GitHub

标签 yii 下的文章

在Yii中使用Captcha验证码

在Yii自带demo里,site/contact中使用了验证码,用法如下

Controller

在SiteController加入映射动作CCaptchaAction,映射到SiteController的目的是获取验证码图片,以及表单校验都会用到这个action的类,因为Yii的验证码的session_key是根据controller生成的(也就是说每个controller的验证码的session_key都不同),所以验证码的生成与验证必须在同一个controller中进行。

    /**
     * Declares class-based actions.
     */
    public function actions()
    {
        return array(
            // captcha action renders the CAPTCHA image displayed on the contact page
            'captcha'=>array(
                'class'=>'CCaptchaAction',
                'backColor'=>0xFFFFFF,
            ),
            // page action renders "static" pages stored under 'protected/views/site/pages'
            // They can be accessed via: index.php?r=site/page&view=FileName
            'page'=>array(
                'class'=>'CViewAction',
            ),
        );
    }
     
    /**
     * Displays the contact page
     */
    public function actionContact()
    {
        $model=new ContactForm;
        if(isset($_POST['ContactForm']))
        {
            $model->attributes=$_POST['ContactForm'];
            if($model->validate())
            {
                $name='=?UTF-8?B?'.base64_encode($model->name).'?=';
                $subject='=?UTF-8?B?'.base64_encode($model->subject).'?=';
                $headers="From: $name <{$model->email}>

".
                    "Reply-To: {$model->email}

".
                    "MIME-Version: 1.0

".
                    "Content-Type: text/plain; charset=UTF-8";
 
                mail(Yii::app()->params['adminEmail'],$subject,$model->body,$headers);
                Yii::app()->user->setFlash('contact','Thank you for contacting us. We will respond to you as soon as possible.');
                $this->refresh();
            }
        }
        $this->render('contact',array('model'=>$model));
    }

Model

将验证码属性添加到model里,并设置验证方法,这样调用model的验证方法时将自动验证验证码是否正确

    //验证码
    public $verifyCode;
 
    /**
     * 定义验证规则
     */
    public function rules()
    {
        return array(
            // name, email, subject and body are required
            array('name, email, subject, body', 'required'),
            ...
            //验证码验证规则
            array('verifyCode', 'captcha', 'allowEmpty'=>!CCaptcha::checkRequirements()),
        );
    }

View

上面两个步骤完成后,就可以在view里加载小部件了

<div class="captcha_box">    
    <?php $this->widget('CCaptcha'); ?>
    <?php echo $form->textField($model,'verifyCode'); ?>
</div>

这样就可以在提交表单的时候使用验证码了

Yii的CSRF验证

在Yii框架中,为了防止csrf攻击,封装了CSRF令牌验证。
只需要在主配置文件中进行简单的配置,就可以实现CSRF的验证。

    'components'=>array(
        'request'=>array(
            // Enable Yii Validate CSRF Token
            'enableCsrfValidation' => true,
        ),
    ),

将enableCsrfValidation设置为true了之后,使用Yii表单生成页面的时候,如果表单的提交方式为POST,是都会在页面中添加一个隐藏字段

<div style="display:none">
    <input type="hidden" value="a429b6c0f4468db23a5661d1682db537fe2672c7" name="YII_CSRF_TOKEN" />
</div>

自己写的表单需要手动添加隐藏字段

<input type="hidden" value="<?php echo Yii::app()->getRequest()->getCsrfToken(); ?>" name="YII_CSRF_TOKEN" />

用户在提交表单的同时,将该字段提交给服务器端,Yii框架会将该有客户端提交过来的隐藏字段和客户端提交过来的Cookie中的YII_CSRF_TOKEN值进行比较。相同则通过继续执行,不相同则会抛出400异常:"The CSRF token could not be verified."。

上面的方法是将客户端提交过来的值和客户端的Cookie中的值进行比较,并不是最为安全的方法。

目前更为安全的方式,是将客户端提交过来的值和Session中的值进行比较,这就需要重写CHttpRequest类了。具体步骤如下:

  1. 重写CHttpRequest:
    创建一个类HttpRequest继承于CHttpRequest,并将该类存放在 protected/components 下。

重写CHttpRequest的 getCsrfToken() 和 validateCsrfToken($event) 方法。

private $_csrfToken;
// 
public function getCsrfToken()
{
    if($this->_csrfToken===null)
    {
        $session = Yii::app()->session;
        $csrfToken=$session->itemAt($this->csrfTokenName);
        if($csrfToken===null)
        {
            $csrfToken = sha1(uniqid(mt_rand(),true));
            $session->add($this->csrfTokenName, $csrfToken);
        }
        $this->_csrfToken = $csrfToken;
    }
  
    return $this->_csrfToken;
}
// 
public function validateCsrfToken($event)
{
    if($this->getIsPostRequest())
    {
        // only validate POST requests
        $session=Yii::app()->session;
        if($session->contains($this->csrfTokenName) && isset($_POST[$this->csrfTokenName]))
        {
            $tokenFromSession=$session->itemAt($this->csrfTokenName);
            $tokenFromPost=$_POST[$this->csrfTokenName];
            $valid=$tokenFromSession===$tokenFromPost;
        }
        else
            $valid=false;
        if(!$valid)
            throw new CHttpException(400,Yii::t('yii','The CSRF token could not be verified.'));
    }
}
  1. 修改配置文件main.php:
    'components' => array(
        'request' => array(
            'class' => 'application.components.HttpRequest',
            'enableCsrfValidation' => true,
        ),
    ),