您的位置:首页 > 编程语言 > PHP开发

YII自带验证的源码分析

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

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()方法,将数据持久化。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: