yii2實現token認證(源碼分析)

筆者在學習用yii2寫restful api的token認證部分遇到困難,官網教程沒看懂~,解決後,記錄之。

yii的RESTful 授權認證

官方教程鏈接,大概意思如下:

  1. yii2提供了3種驗證token方式,需要在具體控制器指定使用哪種(也可以都使用),這裏以QueryParams方式爲例,即通過$_GET參數方式接受token,代碼如下:

    public function behaviors()
    {
       $behaviors = parent::behaviors();
       $behaviors['authenticator'] = [
           'class' => QueryParamAuth::className(),
       ];
       return $behaviors;
    }
  2. 然後官網說,只需實現User類的findIdentityByAccessToken方法,就實現了認證,代碼如下:

    class User extends ActiveRecord implements IdentityInterface
    {
       public static function findIdentityByAccessToken($token, $type = null)
       {
           return static::findOne(['access_token' => $token]);
       }
    }

    最初看到這裏我是一臉懵逼的,findIdentityByAccessToken()的內容我理解,就是用傳入token去用戶表查詢用戶,問題是認證器QueryParamAuth怎麼找到findIdentityByAccessToken()的?

看源碼

  1. 代碼跳轉到QueryParamAuth類,發現其有authenticate()方法,代碼如下:

    public function authenticate($user, $request, $response)
    {
       $accessToken = $request->get($this->tokenParam);
       if (is_string($accessToken)) {
           $identity = $user->loginByAccessToken($accessToken, get_class($this));
           if ($identity !== null) {
               return $identity;
           }
       }
       if ($accessToken !== null) {
           $this->handleFailure($response);
       }
    
       return null;
    }

    推測是通過authenticate()找到User類的,因爲QueryParamAuth只有這個方法~,再看了另外兩種認證方式:HttpBasicAuthHttpBearerAuth也有authenticate()方法(HttpBearerAuth的在其父類裏),就看它吧。

  2. 大概意思是,通過request組件獲取get參數,然後關鍵是user,使用代碼編輯器跳轉找不到loginByAccessToken,怎麼辦?推測它也是組件,因爲後兩個參數看來都是組件,User組件!跳到配置文件,代碼如下:

    'user' => [
       'identityClass' => 'common\models\User',
       'enableAutoLogin' => true,
       'identityCookie' => ['name' => '_identity-frontend', 'httpOnly' => true],
    ],

    Notice:以yii2高級模板爲例的

  3. 沒有看到user組件指定的class,也就是說該組件有默認class,這句話不理解的看這裏。在項目目錄\verdor\yiisoft\yii2\web\Application.php的找到

    public function coreComponents()
    {
      return array_merge(parent::coreComponents(), [
          'request' => ['class' => 'yii\web\Request'],
          'response' => ['class' => 'yii\web\Response'],
          'session' => ['class' => 'yii\web\Session'],
          'user' => ['class' => 'yii\web\User'],
          'errorHandler' => ['class' => 'yii\web\ErrorHandler'],
      ]);
    }
  4. 跳到yii\web\User,發現loginByAccessToken()方法,不記得它的看前面第3點,代碼如下

       public function loginByAccessToken($token, $type = null)
       {
           /* @var $class IdentityInterface */
           $class = $this->identityClass;
           $identity = $class::findIdentityByAccessToken($token, $type);
           if ($identity && $this->login($identity)) {
               return $identity;
           }
    
           return null;
       }

    意思是,調用了identityClass屬性的findIdentityByAccessToken()方法(還記得它嗎!),identityClass屬性在配置文件指定了,看第4點,findIdentityByAccessToken就是第二點看得我一臉懵逼的方法。至此,真相大白。

  5. 還有一點要說明的是,區分第二點的User類與User組件

    • User組件:很好理解,與其它組件一樣,需要指定類,可通過\Yii::$app->User找到,理解組件就能很好理解User組件了。

    • User類:這個類挺複雜的,有2重身份:

      1. 數據庫模型類:它是用戶表對應的模型,所以,對用戶表的增刪改查都通過它來做,這部分與其它數據庫模型類沒區別。
      2. 用戶認證類:要有這一身份必須滿足2個條件
        • 實現yii\web\IdentityInterface接口:implements IdentityInterface
        • 在配置文件User組件裏指定爲其identityClass屬性

      在這一身份角度看,User類是User組件的一部分。

流程總結

控制器指定了認證器 -> 認證器執行authenticate() -> User組件的loginByAccessToken()->User認證類的findIdentityByAccessToken()->根據token去user表找user

yii實現token認證具體做法:

前提:數據庫用戶表有token字段。

  1. 控制器指定認證器
  2. User組件指定identityClass屬性
  3. User類實現IdentityInterface接口,實現findIdentityByAccessToken()方法

另外,可能你有如下需求

  • 不想在每個控制器裏都寫behaviors()聲明認證器
  • 某些接口不需要執行認證

下面是我的做法,供參考:

  1. 建基類控制器BaseController,繼承activeController,聲明認證器:

    public function behaviors()
    {
        $behaviors = parent::behaviors();
        $behaviors['authenticator'] = [
            'class' => HttpBearerAuth::className(), // 聲明認證器
        ];
        return $behaviors;
    }
  2. 在子類控制器,繼承BaseController聲明哪些動作不需要認證:

    public function behaviors()
    {
        $behaviors = parent::behaviors();
        $behaviors['authenticator']['optional'] =
             ['action1', 'action2'];    // 不需要認證的action
        return $behaviors;
    }

總結

筆者通過這個體會到接口的意義,User類實現IdentityInterface,就要實現其findIdentityByAccessToken()方法,否則報錯:接口方法沒實現。爲什麼要設定爲報錯,爲什麼要有 實現了接口,就一定要實現其方法的規定?因爲,很有可能,接口方法在某個地方被調用了!,進而引出對面向對象接口的理解,有興趣的可跳轉:面向對象的接口使用前人代碼的方式

相關鏈接

面向對象的接口使用前人代碼的方式

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章