• Yii2.0登录详解(下)


    在上一篇博文中,笔者讲述了yii2应用用户登陆的基本方法,但是这些方法到底是怎样实现登陆的呢?底层的原理到底是什么?在这篇博文笔者将从Yii的源码角度分析登陆的基本原理以及cookie自动登陆的原理,通过源码的分析,各位对Yii的理解也会更上一层楼。

    一、第一次正常登陆

         1、在LoginForm.PHP中,我们曾经调用了这个方法:

    [php] view plain copy
     
     print?
    1. Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0);  

    由以上可知,调用了user组件的login()方法,把两个参数传递进入,分别是User类实例以及记住时间(Cookie验证会用到,如果传递0,则不启用Cookie验证)。

         2、进入yiiwebuser类中,找到login()方法如下所示:

    [php] view plain copy
     
     print?
    1. public function login(IdentityInterface $identity, $duration = 0)  
    2.     {  
    3.         if ($this->beforeLogin($identity, false, $duration)) {  
    4.             $this->switchIdentity($identity, $duration);     //①  
    5.             $id = $identity->getId();  
    6.             $ip = Yii::$app->getRequest()->getUserIP();  
    7.             if ($this->enableSession) {  
    8.                 $log = "User '$id' logged in from $ip with duration $duration.";  
    9.             } else {  
    10.                 $log = "User '$id' logged in from $ip. Session not enabled.";  
    11.             }  
    12.             Yii::info($log, __METHOD__);  
    13.             $this->afterLogin($identity, false, $duration);  
    14.         }  
    15.   
    16.         return !$this->getIsGuest();  
    17.     }  

     这里关注①号处代码:$this->switchIdentity($identity,$duration).这里调用了当前类的switchIdentity方法,把接受到的两个参数同时传递进去,我们往下看:

    3、switchIdentity($identity,$duration)方法如下:

    [php] view plain copy
     
     print?
    1. public function switchIdentity($identity, $duration = 0)  
    2.     {  
    3. <span style="white-space:pre">    </span>...  
    4. <span style="white-space:pre">    </span>...  
    5.         if ($identity) {  
    6.             ...  
    7.             if ($duration > 0 && $this->enableAutoLogin) {<span style="white-space:pre">    </span>//①  
    8.                 $this->sendIdentityCookie($identity, $duration); //②  
    9.             }  
    10.         } elseif ($this->enableAutoLogin) {  
    11.             Yii::$app->getResponse()->getCookies()->remove(new Cookie($this->identityCookie));  
    12.         }  
    13.     }  

    由于代码过长,笔者做了适当精简,只讨论与登陆和cookie联系最为密切的部分,由①处代码,首先会对duration进行判断,只有大于0的情况下才会进行cookie验证,然后再判断了enableAutoLogin的值,这个值也是cookie验证的关键所在,只有为true的时候才会储存cookie,该值在config/main.php中注册user组件的时候进行初始化,代码如下:

    [php] view plain copy
     
     print?
    1. 'components' => [  
    2.         'user' => [  
    3.             'identityClass' => 'appmodulesackendmodelsUser',  
    4.             'enableAutoLogin' => true,  
    5.         ],]  

    在判断都为真的时候,即进行cookie储存和登陆的时候,进入②号代码,可以看到,调用了sendIdentityCookie()方法。

    4、sendIdentityCookie($identity,$duration):

    [php] view plain copy
     
     print?
    1. protected function sendIdentityCookie($identity, $duration)  
    2.     {  
    3.         $cookie = new Cookie($this->identityCookie);  
    4.         //cookie的value值是json数据  
    5.         $cookie->value = json_encode([  
    6.             $identity->getId(),  
    7.             $identity->getAuthKey(),  
    8.             $duration,  
    9.         ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);  
    10.         //设置cookie的有效时间  
    11.         $cookie->expire = time() + $duration;  
    12.         Yii::$app->getResponse()->getCookies()->add($cookie);  //①  
    13.     }  

    在生成一个cookie的实例以及对cookie进行初始化后,接下来来到了重点部分,即①号代码处,首先getResponse()得到response组件,然后调用response的getCookies方法,返回一个CookieCollection实例,该实例保存了response组件所生成的所有cookie,显然,最后一个add()方法是把当前设置好的cookie保存进CookieCollection实例中。
    到目前为止,yii已经完成了储存cookie的操作,但是,有一点要注意的,利用yii框架的这种方式储存cookie,要记住一点,就是在Controller那里,通过验证后,应该使用redirect()来进行页面跳转,而不应该直接render()渲染布局,否则,当前登陆完成后,cookie将暂时得不到保存,(详细可通过查看浏览的cookie缓存来查看)如果cookie得不到立即的储存,有可能对后续用户的登陆造成未知困扰。

    为什么会出现这样的情况呢?

    我们来看看response组件的redirect()方法,(一般通过Yii::$app->response->redirect()来跳转url):

    [php] view plain copy
     
     print?
    1. public function redirect($url, $statusCode = 302, $checkAjax = true)  
    2.     {  
    3.         ...  
    4.         ...  
    5.   
    6.         return $this;  
    7.     }  

    关键在于最后的return $this;这句表示把当前类作为整个反应返回给客户端,换句话说:即当前保存的所有cookie,以及各种其他属性,在调用了redirect后全部返回。所以,我们在控制器需要使用redirect(),而不是直接使用render()方法。
    综上所述,在完成了user组件的login()方法后,用户的个人信息便保存于user组件中,直到用户关闭浏览器或者退出登录。


    二、利用Cookie登陆

    在用户关闭浏览器的时候(非退出),再次访问登陆页面,会发现页面已经自动跳转到主页,也即是说完成了自动登陆的功能(前提是点击了Remember Me),我们回顾一下logincontroller里面关于登陆的逻辑:

    [php] view plain copy
     
     print?
    1. if (!Yii::$app->user->isGuest) {  
    2.             return $this->goHome();  
    3.         }  

    可以看出,当访问登陆页面的时候,会先执行判断,判断当前用户是否是游客,若不是,则直接跳转到主页。所以关于自动登陆的逻辑便隐藏在:Yii::$app -> user ->isGuest 中,我们查看user组件相关代码:

    [php] view plain copy
     
     print?
    1. public function getIsGuest()  
    2.     {  
    3.         return $this->getIdentity() === null;  
    4.     }  

    在以上方法中,调用了getIdentity()方法:

    [php] view plain copy
     
     print?
    1. public function getIdentity($autoRenew = true)  
    2.     {  
    3.         if ($this->_identity === false) {  
    4.             if ($this->enableSession && $autoRenew) {  
    5.                 $this->_identity = null;  
    6.                 $this->renewAuthStatus();  
    7.             } else {  
    8.                 return null;  
    9.             }  
    10.         }  
    11.   
    12.         return $this->_identity;  
    13.     }  

    由于user组件默认是开始session的,所以enableSession应该为true,所以会执行renewAuthStatus()函数:

    [php] view plain copy
     
     print?
    1. protected function renewAuthStatus()  
    2.     {  
    3.         ...  
    4.         if ($this->enableAutoLogin) {  
    5.             if ($this->getIsGuest()) {  
    6.                 $this->loginByCookie();  
    7.             } elseif ($this->autoRenewCookie) {  
    8.                 $this->renewIdentityCookie();  
    9.             }  
    10.         }  
    11.     }  

    结合之前的enableAutoLogin为true以及当前处于未登录状态,所以getIsGuest()返回真,所以最后会执行loginByCookie()方法,这也是核心所在:

    [php] view plain copy
     
     print?
    1. protected function loginByCookie()  
    2.     {<span style="white-space:pre">   </span>//从客户端读取cookie  
    3.         $value = Yii::$app->getRequest()->getCookies()->getValue($this->identityCookie['name']);  
    4.         if ($value === null) {  
    5.             return;  
    6.         }  
    7. <span style="white-space:pre">    </span>//由于之前储存cookie是用json格式储存,所以现在需要先解析  
    8.         $data = json_decode($value, true);  
    9.         if (count($data) !== 3 || !isset($data[0], $data[1], $data[2])) {  
    10.             return;  
    11.         }  
    12.   
    13.         list ($id, $authKey, $duration) = $data;  
    14.         /* @var $class IdentityInterface */  
    15.         $class = $this->identityClass;<span style="white-space:pre">   </span>//读取当前的用户验证类类名,即实现了Identity接口的类  
    16.         $identity = $class::findIdentity($id);<span style="white-space:pre">  </span>//调用该类的方法,从数据库查找数据  
    17.         if ($identity === null) {  
    18.             return;  
    19.         } elseif (!$identity instanceof IdentityInterface) {  
    20.             throw new InvalidValueException("$class::findIdentity() must return an object implementing IdentityInterface.");  
    21.         }  
    22. <span style="white-space:pre">    </span>//如果数据库提供的auth_key与从客户取得的auth_key相同  
    23.         if ($identity->validateAuthKey($authKey)) {  
    24.             if ($this->beforeLogin($identity, true, $duration)) { //①  
    25.                 $this->switchIdentity($identity, $this->autoRenewCookie ? $duration : 0);  
    26.                 $ip = Yii::$app->getRequest()->getUserIP();  
    27.                 Yii::info("User '$id' logged in from $ip via cookie.", __METHOD__);  
    28.                 $this->afterLogin($identity, true, $duration);  
    29.             }  
    30.         } else {  
    31.             Yii::warning("Invalid auth key attempted for user '$id': $authKey", __METHOD__);  
    32.         }  
    33.     }  

    由①可知,在auth_key验证通过后,所执行的代码基本与首次执行的login()方法相同,即实现了重新登录的功能。

    关于Yii2用户登陆功能的实现以及cookie自动登陆的原理已经全部讲述完毕。

    ps:本文转自他人,不是原创,我还没那技术。

  • 相关阅读:
    Memcached:高性能的分布式内存缓存服务器
    MySQL数据库Query的优化
    MySQL数据库的锁定机制及优化
    系统架构及实现对性能的影响(一)
    Mysql数据库的基本结构和存储引擎简介
    Spring事务管理的回滚
    穷举算法实例
    在写完全二叉树的构建及遍历
    Inotify
    Rsync扩展
  • 原文地址:https://www.cnblogs.com/chenhaoyu/p/5949554.html
Copyright © 2020-2023  润新知