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点,代码如下

    扫描二维码关注公众号,回复: 574227 查看本文章
       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个条件

      3. 实现yii\web\IdentityInterface接口:implements IdentityInterface

      4. 在配置文件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()方法,否则报错:接口方法没实现。为什么要设定为报错,为什么要有 实现了接口,就一定要实现其方法的规定?因为,很有可能,接口方法在某个地方被调用了!,进而引出对面向对象接口的理解,有兴趣的可跳转:面向对象的接口使用前人代码的方式

相关链接

面向对象的接口使用前人代码的方式

猜你喜欢

转载自blog.csdn.net/kbellx/article/details/80279741
今日推荐