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+mysql保存和输出文件
Oct 09 PHP
PHP 导出数据到淘宝助手CSV的方法分享
Feb 27 PHP
php读取文件内容至字符串中,同时去除换行、空行、行首行尾空格(Zjmainstay原创)
Jul 31 PHP
基于curl数据采集之单页面采集函数get_html的使用
Apr 28 PHP
如何使用PHP批量去除文件UTF8 BOM信息
Aug 05 PHP
php 根据url自动生成缩略图并处理高并发问题
Jan 23 PHP
php绘制一个扇形的方法
Jan 24 PHP
PHP使用fopen与file_get_contents读取文件实例分享
Mar 04 PHP
PHP登录(ajax提交数据和后台校验)实例分享
Dec 29 PHP
PHP进阶学习之Geo的地图定位算法详解
Jun 19 PHP
laravel-admin 中列表筛选方法
Oct 03 PHP
PHP设计模式(三)建造者模式Builder实例详解【创建型】
May 02 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教程孙仲岳主讲
2008/01/07 PHP
Symfony实现行为和模板中取得request参数的方法
2016/03/17 PHP
php 截取GBK文档某个位置开始的n个字符方法
2017/03/08 PHP
JQuery学习笔录 简单的JQuery
2012/04/09 Javascript
详解JavaScript正则表达式中的global属性的使用
2015/06/16 Javascript
ES6中非常实用的新特性介绍
2016/03/10 Javascript
Three.js快速入门教程
2016/09/09 Javascript
jQuery插件echarts实现的单折线图效果示例【附demo源码下载】
2017/03/04 Javascript
vue.js实现条件渲染的实例代码
2017/06/22 Javascript
JSON创建键值对(key是中文或者数字)方式详解
2017/08/24 Javascript
js中let和var定义变量的区别
2018/02/08 Javascript
vue中子组件调用兄弟组件方法
2018/07/06 Javascript
解决vue 项目引入字体图标报错、不显示等问题
2018/09/01 Javascript
react native基于FlatList下拉刷新上拉加载实现代码示例
2018/09/30 Javascript
微信小程序-可移动菜单的实现过程详解
2019/06/24 Javascript
一次微信小程序内地图的使用实战记录
2019/09/09 Javascript
Vue中jsx不完全应用指南小结
2019/11/01 Javascript
js中script的上下放置区别,Dom的增删改创建操作实例分析
2019/12/16 Javascript
Vuex实现数据共享的方法
2019/12/20 Javascript
简单总结Python中序列与字典的相同和不同之处
2016/01/19 Python
浅谈Python中range和xrange的区别
2017/12/20 Python
Python 统计字数的思路详解
2018/05/08 Python
Python使用py2neo操作图数据库neo4j的方法详解
2020/01/13 Python
TensorFlow tensor的拼接实例
2020/01/19 Python
Python变量作用域LEGB用法解析
2020/02/04 Python
keras处理欠拟合和过拟合的实例讲解
2020/05/25 Python
Django contrib auth authenticate函数源码解析
2020/11/12 Python
Shop Apotheke瑞士:您的健康与美容网上商店
2019/10/09 全球购物
酒店管理毕业生自我鉴定
2014/03/02 职场文书
护理专业毕业生自我鉴定总结
2014/03/24 职场文书
房地产广告策划方案
2014/05/15 职场文书
我的中国梦演讲稿1000字
2014/08/19 职场文书
乡镇防汛工作汇报
2014/10/28 职场文书
2014年煤矿工作总结
2014/11/24 职场文书
2015年公共机构节能宣传周活动总结
2015/03/26 职场文书
redis三种高可用方式部署的实现
2021/05/11 Redis