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 相关文章推荐
在同一窗体中使用PHP来处理多个提交任务
Oct 09 PHP
PHP4 与 MySQL 数据库操作函数详解
Dec 06 PHP
php 面向对象的一个例子
Apr 12 PHP
如何使用PHP批量去除文件UTF8 BOM信息
Aug 05 PHP
PHP采用XML-RPC构造Web Service实例教程
Jul 16 PHP
PHP格式化MYSQL返回float类型的方法
Mar 30 PHP
PHP单链表的实现代码
Jul 05 PHP
PHP线程的内存回收问题
Jul 08 PHP
Zend Framework校验器Zend_Validate用法详解
Dec 09 PHP
在PHP 7下安装Swoole与Yar,Yaf的方法教程
Jun 02 PHP
PHP基于双向链表与排序操作实现的会员排名功能示例
Dec 26 PHP
Yii2.0框架实现带分页的多条件搜索功能示例
Feb 20 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异常处理技术,顶级异常处理器
2012/06/13 PHP
深入php self与$this的详解
2013/06/08 PHP
php获取网页中图片、DIV内容的简单方法
2014/06/19 PHP
PHP连接数据库实现注册页面的增删改查操作
2016/03/27 PHP
javascript 使用 NodeList需要注意的问题
2013/03/04 Javascript
jQuery Trim去除字符串首尾空字符的实现方法说明
2014/02/11 Javascript
Jquery插件easyUi实现表单验证示例
2015/12/15 Javascript
Vue.js 表单校验插件
2016/08/14 Javascript
JS产生随机数的用法小结
2016/12/10 Javascript
javascript实现用户点击数量统计
2016/12/25 Javascript
js中删除数组中的某一元素实例(无下标时)
2017/02/28 Javascript
jQuery插件FusionCharts实现的2D柱状图效果示例【附demo源码下载】
2017/03/06 Javascript
angularJs的ng-class切换class
2017/06/23 Javascript
vue.js $refs和$emit 父子组件交互的方法
2017/12/20 Javascript
vue 下列表侧滑操作实例代码详解
2018/07/24 Javascript
深入理解NodeJS 多进程和集群
2018/10/17 NodeJs
移动端如何用下拉刷新的方式实现上拉加载
2018/12/10 Javascript
Vue+Express实现登录注销功能的实例代码
2019/05/05 Javascript
vue选项卡切换登录方式小案例
2019/09/27 Javascript
js 数组当前行添加数据方法详解
2020/07/28 Javascript
[50:17]Newbee vs Serenity 2018国际邀请赛小组赛BO2 第二场 8.17
2018/08/18 DOTA
python抓取并保存html页面时乱码问题的解决方法
2016/07/01 Python
python实现微信跳一跳辅助工具步骤详解
2018/01/04 Python
python MNIST手写识别数据调用API的方法
2018/08/08 Python
对DJango视图(views)和模版(templates)的使用详解
2019/07/17 Python
pytorch中如何使用DataLoader对数据集进行批处理的方法
2019/08/06 Python
Python中模块(Module)和包(Package)的区别详解
2019/08/07 Python
如何修复使用 Python ORM 工具 SQLAlchemy 时的常见陷阱
2019/11/19 Python
python无序链表删除重复项的方法
2020/01/17 Python
来自世界各地的优质葡萄酒:VineShop24
2018/07/09 全球购物
自我鉴定范文
2013/11/10 职场文书
学生会离职感言
2014/02/11 职场文书
工地安全质量标语
2014/06/07 职场文书
个人先进事迹总结
2015/02/26 职场文书
2016年大学自主招生自荐信范文
2015/03/24 职场文书
本科毕业答辩开场白
2015/05/27 职场文书