YII自带验证的源码分析
2013-04-03 10:28
435 查看
本来打算写CBaseUserIdentity类的setState()方法与CWebUser类的setState()方法的联系与区别,是因为昨天下午准备删除一些曾经收集的资料,重新看了一下其中一篇名为“Yii登录验证和全局访问用户ID”这篇文章时,碰到了与CWebUser类中同名方法setState(),一时不明白这两个同名方法的联系与区别。晚上抽空看了一源码,搞清楚了他们间的关系,为了叙述方便,在此一并将YII自带验证功能一并分析一下。
SiteController.php
$model->login(),这个方法,要详细说说。
在Boylee视频教程中,告诉我们,对于用户的验证(采用根据数据库方式),需要重写application.components.UserIdentity类中的authenticate方法,下面看一下UserIdentity类
疑惑2:为什么在这里重写authenticate方法,就能验证?
一、CComponent类中getter方法
一个类,只要继承于CComponent类,在这个子类中定义getter方法或setter方法,访问属性就像访问普通的对象变量一样简单,方便。
比如
在application.models.LoginForm类中的ruls()方法中,定义了一个自定义规则
知识补充:
dd
好了,现在内容又集中到一个点上,那就是application.models.LoginForm类中的login()方法:
--------------------------------------------------------------
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方法。在用户会话中根据需要,身份类可能需要定义别的身份信息。
下面是个例子:
CBaseUserIdentity类的setState()方法与CWebUser类的setState()方法的联系与区别
CBaseUserIdentity类的setState()方法,还是先看下源码:
到此,这和持久化存储数据没有一点关系。奇妙的是,在CWebUser类中的login()方法中,它调用了CBaseUserIdentity类的getPersistentStates方法
SiteController.php
public function actionLogin() { $model=new LoginForm; // if it is ajax validation request if(isset($_POST['ajax']) && $_POST['ajax']==='login-form') { echo CActiveForm::validate($model); Yii::app()->end(); } // collect user input data if(isset($_POST['LoginForm'])) { $model->attributes=$_POST['LoginForm']; // validate user input and redirect to the previous page if valid if($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 property public 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 days Yii::app()->user->login($this->_identity,$duration); return true; } else return 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()方法,将数据持久化。
相关文章推荐
- Yii密码加密与验证(源码分析)
- Rails自带用户验证has_secure_password的使用与源码分析
- [开发优化] 系统自带短信程序源码部分分析
- yii 2.0 自带验证与jquery validate结合的一次尝试
- yii框架源码分析之CComponent
- yii框架源码分析之创建controller代码
- yii框架源码分析之创建controller代码
- YII框架分析笔记3:表单模型和验证
- kafka 0.10.1.0 权限验证源码分析
- YII 的源码分析(三)
- [ActiveForm] -- Yii2.0源码分析之——创建表单(ActiveForm)
- setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT) 功能的验证 及其 源码实现分析
- yii源码分析流程
- asp.net mvc源码分析-ModelValidatorProviders 客户端的验证
- Yii框架源码分析之文件结构、程序结构及配置
- mahout算法源码分析之Itembased Collaborative Filtering(三)RowSimilarityJob验证
- DEFAULT_KEYS_SHORTCUT 功能的验证 及其 源码实现分析
- yii自带rbac数据表分析
- asp.net mvc源码分析-ModelValidatorProviders 客户端的验证
- android自带musicplayer源码分析:DeleteItems类