1. 程式人生 > >Yii2.0登入詳解(下)

Yii2.0登入詳解(下)

在上一篇博文中,筆者講述了yii2應用使用者登陸的基本方法,但是這些方法到底是怎樣實現登陸的呢?底層的原理到底是什麼?在這篇博文筆者將從Yii的原始碼角度分析登陸的基本原理以及cookie自動登陸的原理,通過原始碼的分析,各位對Yii的理解也會更上一層樓。

一、第一次正常登陸

     1、在LoginForm.PHP中,我們曾經呼叫了這個方法:

  1. Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0);  

由以上可知,呼叫了user元件的login()方法,把兩個引數傳遞進入,分別是User類例項以及記住時間(Cookie驗證會用到,如果傳遞0,則不啟用Cookie驗證)。

     2、進入yii\web\user類中,找到login()方法如下所示:

  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.         return !$this->getIsGuest();  
  16.     }  

 這裡關注①號處程式碼:$this->switchIdentity($identity,$duration).這裡呼叫了當前類的switchIdentity方法,把接受到的兩個引數同時傳遞進去,我們往下看:

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

  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元件的時候進行初始化,程式碼如下:

  1. 'components' => [  
  2.         'user' => [  
  3.             'identityClass' => 'app\modules\backend\models\User',  
  4.             'enableAutoLogin' => true,  
  5.         ],]  

在判斷都為真的時候,即進行cookie儲存和登陸的時候,進入②號程式碼,可以看到,呼叫了sendIdentityCookie()方法。

4、sendIdentityCookie($identity,$duration):

  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):

  1. public function redirect($url, $statusCode = 302, $checkAjax = true)  
  2.     {  
  3.         ...  
  4.         ...  
  5.         return $this;  
  6.     }  

關鍵在於最後的return $this;這句表示把當前類作為整個反應返回給客戶端,換句話說:即當前儲存的所有cookie,以及各種其他屬性,在呼叫了redirect後全部返回。所以,我們在控制器需要使用redirect(),而不是直接使用render()方法。
綜上所述,在完成了user元件的login()方法後,使用者的個人資訊便保存於user元件中,直到使用者關閉瀏覽器或者退出登入。


二、利用Cookie登陸

在使用者關閉瀏覽器的時候(非退出),再次訪問登陸頁面,會發現頁面已經自動跳轉到主頁,也即是說完成了自動登陸的功能(前提是點選了Remember Me),我們回顧一下logincontroller裡面關於登陸的邏輯:

  1. if (!\Yii::$app->user->isGuest) {  
  2.             return $this->goHome();  
  3.         }  

可以看出,當訪問登陸頁面的時候,會先執行判斷,判斷當前使用者是否是遊客,若不是,則直接跳轉到主頁。所以關於自動登陸的邏輯便隱藏在:Yii::$app -> user ->isGuest 中,我們檢視user元件相關程式碼:

  1. public function getIsGuest()  
  2.     {  
  3.         return $this->getIdentity() === null;  
  4.     }  

在以上方法中,呼叫了getIdentity()方法:

  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.         return $this->_identity;  
  12.     }  

由於user元件預設是開始session的,所以enableSession應該為true,所以會執行renewAuthStatus()函式:

  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()方法,這也是核心所在:

  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.         list ($id, $authKey, $duration) = $data;  
  13.         /* @var $class IdentityInterface */  
  14.         $class = $this->identityClass;<span style="white-space:pre">   </span>//讀取當前的使用者驗證類類名,即實現了Identity介面的類  
  15.         $identity = $class::findIdentity($id);<span style="white-space:pre">  </span>//呼叫該類的方法,從資料庫查詢資料  
  16.         if ($identity === null) {  
  17.             return;  
  18.         } elseif (!$identity instanceof IdentityInterface) {  
  19.             throw new InvalidValueException("$class::findIdentity() must return an object implementing IdentityInterface.");  
  20.         }  
  21. <span style="white-space:pre">    </span>//如果資料庫提供的auth_key與從客戶取得的auth_key相同  
  22.         if ($identity->validateAuthKey($authKey)) {  
  23.             if ($this->beforeLogin($identity, true, $duration)) { //①  
  24.                 $this->switchIdentity($identity, $this->autoRenewCookie ? $duration : 0);  
  25.                 $ip = Yii::$app->getRequest()->getUserIP();  
  26.                 Yii::info("User '$id' logged in from $ip via cookie.", __METHOD__);  
  27.                 $this->afterLogin($identity, true, $duration);  
  28.             }  
  29.         } else {  
  30.             Yii::warning("Invalid auth key attempted for user '$id': $authKey", __METHOD__);  
  31.         }  
  32.     }  

由①可知,在auth_key驗證通過後,所執行的程式碼基本與首次執行的login()方法相同,即實現了重新登入的功能。

關於Yii2使用者登陸功能的實現以及cookie自動登陸的原理已經全部講述完畢。