前一段时间更改了站点的授权架构,今天被通知自动登陆功能不好使了。用的Yii框架,为了找出原因,借着机会把Yii的自动登陆流程理一遍。花了一个上午弄明白了流程,了解了原理之后简单几行代码就把问题解决了。

Yii的自动登陆基于cookie,从cookie中获取用户凭据,验证成功后授权并登陆用户。这篇文章 描述的是Yii的登录流程而非自动登陆流程。在解决过程中参考了这篇文章,以至于被误导了。

鉴于在网上没有找到非常好的Yii自动登陆流程分析资料,本文整理了Yii中用户的登陆流程,希望能对需要的人有所帮助。

流程

  1. 入口文件index.php创建了CWebApplication应用。当用到user组件时会通过Yii::app()->user方式获取。
  2. user不是CWebApplication的成员,由于在其父级(framework/base/CModule.php)中实现了 __get 方法,实际上访问的是getUser()方法;
  3. 打开 framework/web/CWebApplication,找到 getUser 方法,其原型为:
    public function getUser()
    {
      return $this->getComponent('user');
    }
    

该方法将请求委托给getComponent函数。

    1. 接着看 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);.

    2. user的配置在文件(protected/config/main)中,默认的实例类型为系统实现的CWebUser。另外可配置是否开启自动登录,登录网址等;
    3. 组件创建完毕后,开始执行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属性,就进入 restoreFromCookie。

  1. 同样在 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

  2. 默认 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。

  3. 用户授权完成之后,就是 afterLogin 后处理方法。方法可以用来做一些后续工作,比如记录用户的登录时间,IP。至此,用户自动登陆工作全部完成。

对比用户名密码登陆,自动登录是基于cookie的。校验cookie通过后,生成授权实例,再登录系统。

一些要点

  • 根据源码跟踪,在 UserIdentity 里的属性都会被序列化之后存储到cookie里面,同时这些属性也会放到用户的session中。默认的属性是id和name,可以根据需要将其他属性放入,例如用户角色。
  • 重写 afterLogin 方法是必要的,可以在自动登陆之后做许多的事情,例如根据用户角色实例化用户对象等。

以上就是个人捕捉到的一些细节,如果有误欢迎指正。