YII自带验证的源码分析

来源:互联网 发布:高级网络优化工程师 编辑:程序博客网 时间:2024/05/01 06:15

本来打算写CBaseUserIdentity类的setState()方法与CWebUser类的setState()方法的联系与区别,是因为昨天下午准备删除一些曾经收集的资料,重新看了一下其中一篇名为“Yii登录验证和全局访问用户ID”这篇文章时,碰到了与CWebUser类中同名方法setState(),一时不明白这两个同名方法的联系与区别。晚上抽空看了一源码,搞清楚了他们间的关系,为了叙述方便,在此一并将YII自带验证功能一并分析一下。

SiteController.php

public function actionLogin(){$model=new LoginForm;// if it is ajax validation requestif(isset($_POST['ajax']) && $_POST['ajax']==='login-form'){echo CActiveForm::validate($model);Yii::app()->end();}// collect user input dataif(isset($_POST['LoginForm'])){$model->attributes=$_POST['LoginForm'];// validate user input and redirect to the previous page if validif($model->validate() && $model->login())$this->redirect(Yii::app()->user->returnUrl);}// display the login form$this->render('login',array('model'=>$model));}
当提交表单时,将会执行:

$model->validate() && $model->login()
$model->validate()就是根据LoginForm中的rules()方法中定义的规则进行验证,这里不解释。

$model->login(),这个方法,要详细说说。

在Boylee视频教程中,告诉我们,对于用户的验证(采用根据数据库方式),需要重写application.components.UserIdentity类中的authenticate方法,下面看一下UserIdentity类

class UserIdentity extends CUserIdentity{private $_id;public function authenticate(){//User::model() 获得User的一个实例$username=strtolower($this->username);$user=User::model()->find('LOWER(username)=?',array($username));if($user===null){$this->errorCode=self::ERROR_USERNAME_INVALID;$this->errorMessage='帐号不存在';}else{//if(!user::model()->validatePassword($this->password)){if(!$user->validatePassword($this->password)){$this->errorCode=self::ERROR_PASSWORD_INVALID;$this->errorMessage='密码错误';}else{$this->_id=$user->id;//Yii::app()->user->id;$this->username=$user->username;$this->errorCode=self::ERROR_NONE;}}return $this->errorCode===self::ERROR_NONE;}public function getId(){return $this->_id;}}
疑惑1:getId()方法是什么意思?这里的getId()方法是CComponent类中getter方法的实现?

疑惑2:为什么在这里重写authenticate方法,就能验证?

一、CComponent类中getter方法

一个类,只要继承于CComponent类,在这个子类中定义getter方法或setter方法,访问属性就像访问普通的对象变量一样简单,方便。

比如

$a=$component->text;     // equivalent to $a=$component->getText();$component->text='abc';  // equivalent to $component->setText('abc');
getter和setter方法的格式如下

// getter, defines a readable property 'text'public function getText() { ... }// setter, defines a writable property 'text' with $value to be set to the propertypublic function setText($value) { ... }
二、验证流程

在application.models.LoginForm类中的ruls()方法中,定义了一个自定义规则

array('password', 'authenticate'),
dd

知识补充:

rules() 返回的每个规则必须是以下格式:array('AttributeList', 'Validator', 'on'=>'ScenarioList', ...附加选项)其中:AttributeList(特性列表)是需要通过此规则验证的特性列表字符串,每个特性名字由逗号分隔;Validator(验证器) 指定要执行验证的种类;on 参数是可选的,它指定此规则应被应用到的场景列表;附加选项是一个名值对数组,用于初始化相应验证器的属性值。有三种方式可在验证规则中指定Validator:第一, Validator 可以是模型类中一个方法的名字,就像上面示例中的authenticate 。验证方法必须是下面的结构:/*** @param string 所要验证的特性的名字* @param array 验证规则中指定的选项*/public function 验证器名称($attribute,$params) { ... }

dd

public function authenticate($attribute,$params){if(!$this->hasErrors()){$this->_identity=new UserIdentity($this->username,$this->password);if(!$this->_identity->authenticate())//$this->addError('password','Incorrect username or password.');$this->addError('password',$this->_identity->errorMessage);}}
这里执行了application.components.UserIdentity类中的authenticate方法

好了,现在内容又集中到一个点上,那就是application.models.LoginForm类中的login()方法:

public function login(){if($this->_identity===null){$this->_identity=new UserIdentity($this->username,$this->password);$this->_identity->authenticate();}if($this->_identity->errorCode===UserIdentity::ERROR_NONE){$duration=$this->rememberMe ? 3600*24*30 : 0; // 30 daysYii::app()->user->login($this->_identity,$duration);return true;}elsereturn false;}
这个方法中,重点又是:Yii::app()->user->login()方法,先看一下这个方法的源码

public function login($identity,$duration=0){    $id=$identity->getId();//执行在application.components.UserIdentity中重写的getId()方法,获取用户数据库的id    $states=$identity->getPersistentStates();//返回需要持久化的身份状态-->设置用setPersistentStates方法    if($this->beforeLogin($id,$states,false))//beforeLogin() 在用户登录那一时刻前被调用的方法。    {        $this->changeIdentity($id,$identity->getName(),$states);//changeIdentity() 用指定的标识符信息来改变当前的用户。        if($duration>0)        {            if($this->allowAutoLogin)                $this->saveToCookie($duration);//保存必要的用户的数据到一个cookie            else                throw new CException(Yii::t('yii','{class}.allowAutoLogin must be set true in order to use cookie-based authentication.',                    array('{class}'=>get_class($this))));        }        $this->afterLogin(false);//用户成功登录后被调用的方法。参数false,表示 不基于cookie登陆的    }    return !$this->getIsGuest();//当前应用程序用户是否是一个来宾用户,这是根据getId()方法中返回的值来决定的,看源码}
getPersistentStates() 方法public array getPersistentStates(){return} array 需要持久化的身份状态。源码: framework/web/auth/CBaseUserIdentity.php#78 (隐藏)public function getPersistentStates(){    return $this->_state;}返回需要持久化的身份状态。此方法为接口IUserIdentity强制要求实现。setPersistentStates() 设置持久化状态数组。setPersistentStates() 方法public void setPersistentStates(array $states)$states array 需要持久化的身份状态。源码: framework/web/auth/CBaseUserIdentity.php#88 (隐藏)public function setPersistentStates($states){    $this->_state = $states;}设置持久化状态数组。
beforeLogin() 方法(可用自 v1.1.3)protected boolean beforeLogin(mixed $id, array $states, boolean $fromCookie)$id mixed 用户ID。这个和getId()方法返回的是一样的。$states array 用户标识(user identity)提供的键名-键值形式的数组。$fromCookie boolean 是否为基于cookie的登录{return} boolean 用户是否可以登录源码: framework/web/auth/CWebUser.php#391 (隐藏)protected function beforeLogin($id,$states,$fromCookie){    return true;}在用户登录那一时刻前被调用的方法。你可以重写该方法来做一些额外的安全检查。例如, 当基于cookie登录时, 你可能想要验证保存在用户ID对应状态的随机令牌是否可以在数据库中找到。这将防止黑客伪造cookie,即使他们获取了服务器私钥。
changeIdentity() 方法protected void changeIdentity(mixed $id, string $name, array $states)$id mixed 用户的唯一标识符$name string 用户的显示的名称$states array 身份信息数组源码: framework/web/auth/CWebUser.php#696 (隐藏)protected function changeIdentity($id,$name,$states){    Yii::app()->getSession()->regenerateID();    $this->setId($id);    $this->setName($name);    $this->loadIdentityStates($states);//loadIdentityStates() 从一个数组加载身份信息并保存到持久的存储中}用指定的标识符信息来改变当前的用户。该方法被login和restoreFromCookie 调用,在当前用户需要填充身份信息时。派生类可以重写该方法,来获取更多的用户相关信息。确保首先调用父类实现。
loadIdentityStates() 方法protected void loadIdentityStates(array $states)$states array 身份信息数组源码: framework/web/auth/CWebUser.php#720 (隐藏)protected function loadIdentityStates($states){    $names=array();    if(is_array($states))    {        foreach($states as $name=>$value)        {            $this->setState($name,$value);//setState() 在用户会话中存储一个变量。            $names[$name]=true;        }    }    $this->setState(self::STATES_VAR,$names);}从一个数组加载身份信息并保存到持久的存储中
setState() 方法public void setState(string $key, mixed $value, mixed $defaultValue=NULL)$key string 变量名$value mixed 变量值$defaultValue mixed 默认值。如果$value===$defaultValue,变量将从会话中移除源码: framework/web/auth/CWebUser.php#576 (隐藏)public function setState($key,$value,$defaultValue=null){    $key=$this->getStateKeyPrefix().$key;    if($value===$defaultValue)        unset($_SESSION[$key]);    else        $_SESSION[$key]=$value;}在用户会话中存储一个变量。CWebUser子类使用此功能设计,是希望更多的用户信息存储在用户会话中。通过此方法存储一个变量,变量可以用 getState取出。变量在整个用户会话期间的页面请求中是持久的。
afterLogin() 方法(可用自 v1.1.3)protected void afterLogin(boolean $fromCookie)$fromCookie boolean 是否是基于cookie登陆的。源码: framework/web/auth/CWebUser.php#403 (隐藏)protected function afterLogin($fromCookie){}用户成功登录后被调用的方法。你可以覆盖这个方法做一些其它处理(如,记录用户的登陆ip和登陆时间,加载用户的信息等)。
saveToCookie() 方法protected void saveToCookie(integer $duration)$duration integer 用户保持登陆状态的秒数。默认为0,意味着登陆状态持续到用户关闭浏览器。源码: framework/web/auth/CWebUser.php#491 (隐藏)protected function saveToCookie($duration){    $app=Yii::app();    $cookie=$this->createIdentityCookie($this->getStateKeyPrefix());    $cookie->expire=time()+$duration;    $data=array(        $this->getId(),        $this->getName(),        $duration,        $this->saveIdentityStates(),    );    $cookie->value=$app->getSecurityManager()->hashData(serialize($data));    $app->getRequest()->getCookies()->add($cookie->name,$cookie);}保存必要的用户的数据到一个cookie。此方法用于自动登陆(allowAutoLogin)启用时。此方法保存用户ID,用户名,其它的身份信息和一个有效的key到cookie。这些信息在用户下次访问应用时认证时使用。
public boolean getIsGuest(){return} boolean 当前应用程序用户是否是一个来宾用户。源码: framework/web/auth/CWebUser.php#277 (隐藏)public function getIsGuest(){    return $this->getState('__id')===null;}

--------------------------------------------------------------

Yii登录验证和全局访问用户ID
Yii 有一个内置的验证/授权(auth)框架,用起来很方便,还能对其进行自定义,使其符合特殊的需求。
Yii auth 框架的核心是一个预定义的 用户(user)应用组件 它是一个实现了 IWebUser 接口的对象。此用户组件代表当前用户的持久性认证信息。我们可以通过Yii::app()->user在任何地方访问它。
使用此用户组件,我们可以通过 CWebUser::isGuest 检查检查一个用户是否登陆; 可以 登录(login) 或 注销(logout) 一个用户;我们可以通过CWebUser::checkAccess检查此用户是否可以执行特定的操作;还可以获取此用户的唯一标识(unique identifier)及其他持久性身份信息。

为了验证一个用户,我们定义一个有验证逻辑的身份类。这个身份类实现IUserIdentity 接口。
不同的类可能实现不同的验证方式(例如:OpenID,LDAP)。最好是继承 CUserIdentity,此类是基于用户名和密码的验证方式。
定义身份类的主要工作是实现IUserIdentity::authenticate方法。在用户会话中根据需要,身份类可能需要定义别的身份信息。
下面是个例子:

class UserIdentity extends CUserIdentity{private$_id;public function authenticate(){$record=User::model()->findByAttributes(array('username'=>$this->username));        if($record===null)$this->errorCode=self::ERROR_USERNAME_INVALID;        elseif($record->password!==md5($this->password))$this->errorCode=self::ERROR_PASSWORD_INVALID;        else{$this->_id=$record->id;            $this->setState('title', $record->title);//=============>            $this->errorCode=self::ERROR_NONE;        }return !$this->errorCode;    }publicfunctiongetId(){return $this->_id;    }}
注意:Yii默认的代码,Yii::app()->user->id返回的不是我们想要的用户ID,而是用户名。因此在useridentity类中要用一个变量来存储登录用户的ID,然后重载getID()方法,返回正确的用户ID。

CBaseUserIdentity类的setState()方法与CWebUser类的setState()方法的联系与区别

CBaseUserIdentity类的setState()方法,还是先看下源码:

setState() 方法 public void setState(string $name, mixed $value) $name string 状态名字 $value mixed 指定名字的状态值 源码: framework/web/auth/CBaseUserIdentity.php#119 (隐藏)public function setState($name,$value){    $this->_state[$name]=$value;} 设置指定状态的值。
它是将$value存到$name为key的_state这个私有的数组变量中。

到此,这和持久化存储数据没有一点关系。奇妙的是,在CWebUser类中的login()方法中,它调用了CBaseUserIdentity类的getPersistentStates方法

$states=$identity->getPersistentStates();
然后,它调用了$this->changeIdentity()方法,将数据持久化。

原创粉丝点击