前一段时间更改了站点的授权架构,今天被通知自动登陆功能不好使了。用的Yii框架,为了找出原因,借着机会把Yii的自动登陆流程理一遍。花了一个上午弄明白了流程,了解了原理之后简单几行代码就把问题解决了。
Yii的自动登陆基于cookie,从cookie中获取用户凭据,验证成功后授权并登陆用户。这篇文章 描述的是Yii的登录流程而非自动登陆流程。在解决过程中参考了这篇文章,以至于被误导了。
鉴于在网上没有找到非常好的Yii自动登陆流程分析资料,本文整理了Yii中用户的登陆流程,希望能对需要的人有所帮助。
流程
- 入口文件index.php创建了CWebApplication应用。当用到user组件时会通过
Yii::app()->user
方式获取。 - user不是CWebApplication的成员,由于在其父级(framework/base/CModule.php)中实现了
__get
方法,实际上访问的是getUser()方法; - 打开
framework/web/CWebApplication
,找到getUser
方法,其原型为:public function getUser() { return $this->getComponent('user'); }
该方法将请求委托给getComponent函数。
- 接着看
getComponent
方法的实现。方法位于framework/base/CModule.php
文件中,其原型为:public function getComponent($id, $createIfNull=true) { if(isset($this->_components[$id])) return $this->_components[$id]; else if(isset($this->_componentConfig[$id]) && $createIfNull) { $config=$this->_componentConfig[$id]; if(!isset($config['enabled']) || $config['enabled']) { Yii::trace("Loading \"$id\" application component",'system.CModule'); unset($config['enabled']); $component=Yii::createComponent($config); $component->init(); return $this->_components[$id]=$component; } } }
第二个参数默认为true,指明如果其不存在则自动创建组件。当用户访问站点,后台获取用户信息访问
user
组件就会触发该段代码。如果user对象存在,直接返回,如果不存在,根据配置创建组件:$component=Yii::createComponent($config);
. - user的配置在文件(protected/config/main)中,默认的实例类型为系统实现的CWebUser。另外可配置是否开启自动登录,登录网址等;
- 组件创建完毕后,开始执行
init
方法。在framework/web/auth/CWebUser.php
下找到init
方法,其原型为:public function init() { parent::init(); Yii::app()->getSession()->open(); if($this->getIsGuest() && $this->allowAutoLogin) $this->restoreFromCookie(); else if($this->autoRenewCookie && $this->allowAutoLogin) $this->renewCookie(); if($this->autoUpdateFlash) $this->updateFlash(); $this->updateAuthStatus();
- 接着看
如果配置开启了 allowAutoLogin
属性,就进入 restoreFromCooki
e。
- 同样在
framework/web/auth/CWebUser.php
文件里,restoreFromCookie
的实现如下:protected function restoreFromCookie() { $app=Yii::app(); $request=$app->getRequest(); $cookie=$request->getCookies()->itemAt($this->getStateKeyPrefix()); if($cookie && !empty($cookie->value) && is_string($cookie->value) && ($data=$app->getSecurityManager()->validateData($cookie->value))!==false) { $data=@unserialize($data); if(is_array($data) && isset($data[0],$data[1],$data[2],$data[3])) { list($id,$name,$duration,$states)=$data; if($this->beforeLogin($id,$states,true)) { $this->changeIdentity($id,$name,$states); if($this->autoRenewCookie) { $cookie->expire=time()+$duration; $request->getCookies()->add($cookie->name,$cookie); } $this->afterLogin(true); } } } }
该方法的逻辑为: 取出用户的cookie信息-》进行合法性校验-》解压数据-》准备登录
beforeLogin
。 - 默认
beforeLogin
直接返回true, 接着进入了changeIdentity
的阶段。下面是该函数的实现:protected function changeIdentity($id,$name,$states) { Yii::app()->getSession()->regenerateID(true); $this->setId($id); $this->setName($name); $this->loadIdentityStates($states); }
主要是加载用户的id和username。此时,系统已经可以通过
Yii::app()->user
获取到用户的信息,isGuest
函数返回false。 - 用户授权完成之后,就是
afterLogin
后处理方法。方法可以用来做一些后续工作,比如记录用户的登录时间,IP。至此,用户自动登陆工作全部完成。
对比用户名密码登陆,自动登录是基于cookie的。校验cookie通过后,生成授权实例,再登录系统。
一些要点
- 根据源码跟踪,在
UserIdentity
里的属性都会被序列化之后存储到cookie里面,同时这些属性也会放到用户的session中。默认的属性是id和name,可以根据需要将其他属性放入,例如用户角色。 - 重写 afterLogin 方法是必要的,可以在自动登陆之后做许多的事情,例如根据用户角色实例化用户对象等。
以上就是个人捕捉到的一些细节,如果有误欢迎指正。
发表回复