Yii框架登录流程分析


Posted in PHP onDecember 03, 2014

本文详细分析了Yii框架的登录流程。分享给大家供大家参考。具体分析如下:

Yii对于新手来说上手有点难度,特别是关于session,cookie和用户验证。现在我们就Yii中登录流程,来讲讲Yii开发中如何设置session,cookie和用户验证方面的一些通用知识

1. 概述

Yii是一个全栈式的MVC框架,所谓全栈式指的是Yii框架本身实现了web开发中所要用到的所有功能,比如MVC,ORM(DAO/ActiveRecord), 全球化(I18N/L10N), 缓存(caching), 基于jQuery Ajax支持(jQuery-based AJAX support), 基于角色的用户验证(authentication and role-based access control), 程序骨架生成器(scaffolding), 输入验证(input validation), 窗体小部件(widgets), 事件(events), 主题(theming), web服务(Web services),日志(logging)等功能. 详见官方说明.

这里要说的只是Yii的登录流程. 用Yii开发一般是用一个叫做Yii shell的控制台工具生成一个程序的骨架,这个骨架为我们分配好了按MVC方式开发web程序的基本结构,并且是一个可以直接运行的程序. 如果你了解 Ruby on Rails的话,原理是一样的.

2.网站登录流程

生成的程序中有一个protected目录,下面的controllers目录有个叫SiteController.php的文件,这个文件是自动生成的,里面有一个叫actionLogin的文件.程序登录流程默认就是从来开始的. Yii把类似于 http://domain.com/index.php?r=site/login 这样的地址通过叫router的组件转到上面说的actionLogin方法里的. 这个路由的功能不是的这里说的重点.actionLogin方法的代码是这样的.

public function actionLogin(){

$model=new LoginForm;

// 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));

}

首先初始化一个LoginForm类,然后判断是否是用户点了登录后的请求(查看请求中有没有POST数据),如果是的话则先验证输入($model->validate)然后尝试登录($model->logiin),如果都成功,就跳转到登录前的页面,否则显示登录页面.

3.框架登录流程

LoginForm类继承于CFormModel,间接继承于CModel,所以他提供了CModel提供了一些像验证和错误处理方面的功能.其中的login方法就是执行验证操作的.方法首先通过用户提供的用户名和密码生成一个用来表示用户实体的UserIdentity类,该类中的authenticate方法执行实际的验证动作,比如从数据库中判断用户名和密码是否匹配. LoginForm类的login方法通过查询authenticate是否有错误发生来判断登录是否成功.如果成功则执行Yii::app()->user->login方法来使用户真正的的登录到系统中. 前面讲到的这些流程是用户程序提供的,而Yii::app()->user->login也就是CWebUser的login方法是Yii框架提供的流程.我们来看看他做了些什么.下面是该方面的代码,位于(Yii)webauthCWebUser.php文件中.

public function login($identity,$duration=0){

$this->changeIdentity($identity->getId(),$identity->getName(),$identity->getPersistentStates());

if($duration>0){

if($this->allowAutoLogin)

$this->saveToCookie($duration);

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))));

}

}

参数$identity是上面登录时生成的UserIdentity类,里面包含了基本的用户信息,如上面的Id,Name,还有可能是其它自定义的数据getPersistentStates. 程序首先把$identity中的数据复制到CWebUser的实例中,这个过程包括了生成相应的session,其实主要目的是生成session.然后根据参数$duration(cookie保存的时间)和allowAutoLogin属性来判断是否生成可以用来下次自动登录的cookie.如果是则生成cookie(saveToCookie).

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);

}

首先是新建一个CHttpCookie,cookie的key通过getStateKeyPrefix方法取得,该方法默认返回md5('Yii.'.get_class($this).'.'.Yii::app()->getId());即类名和CApplication的Id,这个Id又是crc32函数生成的一个值.这个具体的值是多少无关紧要. 但是每次都是产生一样的值的. 接着设置expire,cookie的过期时间,再新建一个array,包含了基本数据,接着比较重要的是计算取得cookie的值,$app->getSecurityManager()->hashData(serialize($data)), getSecurityManager返回一个CSecurityManager的对象,并调用hashData方法.

public function hashData($data){

$hmac=$this->computeHMAC($data);

return $hmac.$data;

}
protected function computeHMAC($data){

if($this->_validation==='SHA1'){

$pack='H40';

$func='sha1';

}

else{

$pack='H32';

$func='md5';

}

$key=$this->getValidationKey();

$key=str_pad($func($key), 64, chr(0));

return $func((str_repeat(chr(0x5C), 64) ^ substr($key, 0, 64)) . pack($pack, $func((str_repeat(chr(0x36), 64) ^ substr($key, 0, 64)) . $data)));

}

hashData调用computHMAC方法生成一个hash值. hash的算法有SHA1和MD5两种,默认是用SHA1的. hash的时候还要生成一个validationKey(验证码)的,然后把验证码和要hash的值进行一些故意安排的运算,最终生成一个40位的SHA1,hash值.  hashData方法最终返回的是computeHMAC生成的hash值和经序列化的原始数据生成的字符串.这个过程有也许会有疑问. 如为什么要有验证码?

我们先来看一下基于cookie的验证是怎么操作的.服务器生成一个cookie后发送的浏览器中,并根据过期时间保存在浏览器中一段时间.用户每次通过浏览器访问这个网站的时候都会随HTTP请求把cookie发送过去,这是http协议的一部分, 与语言和框架无关的. 服务器通过判断发过来的cookie来决定该用户是否可以把他当作已登录的用户.但是cookie是客户端浏览器甚至其它程序发过来的,也就是说发过来的cookie可能是假的中被篡改过的.所以服务器要通过某种验证机制来判断是否是之后自己发过去的cookie.这个验证机制就是在cookie中包含一个hash值和生成这串hash值的原始数据.服务器接到cookie后取出原始数据,然后按原来的方法生成一个hash值来和发过来的hash值比较,如果相同,则信任该cookie,否则肯定是非法请求.比如我的Yii网站生成了这样一个cookie:

cookie name:b72e8610f8decd39683f245d41394b56

cookie value: 1cbb64bdea3e92c4ab5d5cb16a67637158563114a%3A4%3A%7Bi%3A0%3Bs%3A7%3A%22maxwell%22%3Bi%3A1%3Bs%3A7%3A%22maxwell%22%3Bi%3A2%3Bi%3A3600%3Bi%3A3%3Ba%3A2%3A%7Bs%3A8%3A%22realname%22%3Bs%3A6%3A%22helloc%22%3Bs%3A4%3A%22myId%22%3Bi%3A123%3B%7D%7D

cookie name是网站统一生成的一个md5值. cookie value的值为两个部分,就是hashData方法生成的一个字符串.前面部分是hash值,后面是原始值.也就是说前面的1cbb64bdea3e92c4ab5d5cb16a67637158563114是hash值,后面是原始值.这个hash值是用SHA1生成的40位的字符串. 服务器把后面的原始值通过算法hash出一个值和这个传过来的hash值比较就知道是合法不审非法请求了. 那验证码呢?

如果服务器只是简单的把后面的原始值直接用SHA1或MD5,hash的话,那发送请求的人可以随意修改这个原始值和hash值来通过服务器的验证.因为SHA1算法是公开的,每个人都可以使用. 所以服务端需要在hash的时候加一个客户端不知道的验证码来生成一个客户端无法通过原始值得到正确hash的hash值(有点绕:) ). 这就是需要验证码的原因.并且这个验证码必须是全站通用的,所以上面的getValidationKey方法是生成一个全站唯一的验证码并保存起来.默认情况下,验证码是一个随机数,并保存在(yii)runtimestate.bin文件中.这样对每个请求来说都是一样的.

登录流程的最后就是把生成的cookie发送到浏览器中. 下次请求的时候可以用来验证.

希望本文所述对大家基于Yii框架的PHP程序设计有所帮助。

PHP 相关文章推荐
递归列出所有文件和目录
Oct 09 PHP
收藏的一个php小偷的核心程序
Apr 09 PHP
《PHP编程最快明白》第六讲:Mysql数据库操作
Nov 01 PHP
php在window iis的莫名问题的测试方法
May 14 PHP
PHP实现根据图片色界在不同位置加水印的方法
Aug 08 PHP
PHP获取网站中各文章的第一张图片的代码示例
May 20 PHP
深入理解PHP原理之执行周期分析
Jun 01 PHP
Yii实现Command任务处理的方法详解
Jul 14 PHP
PHP new static 和 new self详解
Feb 19 PHP
Yii框架实现图片上传的方法详解
May 20 PHP
PHP设计模式入门之迭代器模式原理与实现方法分析
Apr 26 PHP
Laravel服务容器绑定的几种方法总结
Jun 14 PHP
Yii框架获取当前controlle和action对应id的方法
Dec 03 #PHP
PHP多线程类及用法实例
Dec 03 #PHP
php提取字符串中网站url地址的方法
Dec 03 #PHP
thinkphp常见路径用法分析
Dec 02 #PHP
ThinkPHP中关联查询实例
Dec 02 #PHP
ThinkPHP实现支付宝接口功能实例
Dec 02 #PHP
ThinkPHP实现ajax仿官网搜索功能实例
Dec 02 #PHP
You might like
PHP中::、->、self、$this几种操作符的区别介绍
2013/04/24 PHP
CL vs ForZe BO5 第三场 2.13
2021/03/10 DOTA
node.js中的console.trace方法使用说明
2014/12/09 Javascript
jQuery搜索同辈元素方法
2015/02/10 Javascript
JavaScript+html5 canvas实现图片破碎重组动画特效
2016/02/22 Javascript
nodejs判断文件、文件夹是否存在及删除的方法
2017/11/10 NodeJs
javascript 通过键名获取键盘的keyCode方法
2017/12/31 Javascript
React Native中NavigatorIOS组件的简单使用详解
2018/01/27 Javascript
vue实现条件判断动态绑定样式的方法
2018/09/29 Javascript
nuxt配置通过指定IP和端口访问的实现
2020/01/08 Javascript
[01:32]完美世界DOTA2联赛10月29日精彩集锦
2020/10/30 DOTA
linux系统使用python监测网络接口获取网络的输入输出
2014/01/15 Python
对于Python的Django框架部署的一些建议
2015/04/09 Python
Python使用django获取用户IP地址的方法
2015/05/11 Python
Python实现比较两个文件夹中代码变化的方法
2015/07/10 Python
浅谈Python2.6和Python3.0中八进制数字表示的区别
2017/04/28 Python
利用python将图片转换成excel文档格式
2017/12/30 Python
Python基于property实现类的特性操作示例
2018/06/15 Python
python爬虫之线程池和进程池功能与用法详解
2018/08/02 Python
浅析Python函数式编程
2018/10/06 Python
Python企业编码生成系统之系统主要函数设计详解
2019/07/26 Python
python 循环数据赋值实例
2019/12/02 Python
在 Python 中接管键盘中断信号的实现方法
2020/02/04 Python
Java如何基于wsimport调用wcf接口
2020/06/17 Python
python使用re模块爬取豆瓣Top250电影
2020/10/20 Python
python在地图上画比例的实例详解
2020/11/13 Python
欧姆龙医疗保健与医疗产品:Omron Healthcare
2020/02/10 全球购物
加拿大著名的奢侈品购物网站:SSENSE(支持中文)
2020/06/25 全球购物
公务员中国梦演讲稿
2014/08/19 职场文书
四查四看整改措施
2014/09/19 职场文书
人大代表选举标语
2014/10/07 职场文书
2014学习十八届四中全会精神思想汇报范文
2014/10/23 职场文书
五好家庭事迹材料
2014/12/20 职场文书
祝寿主持词
2015/07/02 职场文书
幽默导游词应该怎么写?
2019/08/26 职场文书
教你漂亮打印Pandas DataFrames和Series
2021/05/29 Python