笔者在学习用yii2写restful api的token认证部分遇到困难,官网教程没看懂~,解决后,记录之。
yii的RESTful 授权认证
官方教程链接,大概意思如下:
yii2提供了3种验证token方式,需要在具体控制器指定使用哪种(也可以都使用),这里以
QueryParams
方式为例,即通过$_GET参数方式接受token,代码如下:public function behaviors() { $behaviors = parent::behaviors(); $behaviors['authenticator'] = [ 'class' => QueryParamAuth::className(), ]; return $behaviors; }
然后官网说,只需实现
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()
的?
看源码
代码跳转到
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
只有这个方法~,再看了另外两种认证方式:HttpBasicAuth
和HttpBearerAuth
也有authenticate()
方法(HttpBearerAuth
的在其父类里),就看它吧。大概意思是,通过
request
组件获取get参数,然后关键是user,使用代码编辑器跳转找不到loginByAccessToken
,怎么办?推测它也是组件,因为后两个参数看来都是组件,User
组件!跳到配置文件,代码如下:'user' => [ 'identityClass' => 'common\models\User', 'enableAutoLogin' => true, 'identityCookie' => ['name' => '_identity-frontend', 'httpOnly' => true], ],
Notice:以yii2高级模板为例的
没有看到
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'], ]); }
跳到
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
就是第二点看得我一脸懵逼的方法。至此,真相大白。还有一点要说明的是,区分第二点的
User
类与User
组件User
组件:很好理解,与其它组件一样,需要指定类,可通过\Yii::$app->User
找到,理解组件就能很好理解User
组件了。User
类:这个类挺复杂的,有2重身份:- 数据库模型类:它是用户表对应的模型,所以,对用户表的增删改查都通过它来做,这部分与其它数据库模型类没区别。
- 用户认证类:要有这一身份必须满足2个条件
- 实现
yii\web\IdentityInterface
接口:implements IdentityInterface
- 在配置文件
User
组件里指定为其identityClass
属性
- 实现
在这一身份角度看,User类是User组件的一部分。
流程总结
控制器指定了认证器 -> 认证器执行authenticate()
-> User
组件的loginByAccessToken()
->User
认证类的findIdentityByAccessToken()
->根据token去user表找user
yii实现token认证具体做法:
前提:数据库用户表有token
字段。
- 控制器指定认证器
User
组件指定identityClass
属性User
类实现IdentityInterface
接口,实现findIdentityByAccessToken()
方法
另外,可能你有如下需求
- 不想在每个控制器里都写
behaviors()
声明认证器 - 某些接口不需要执行认证
下面是我的做法,供参考:
建基类控制器
BaseController
,继承activeController
,声明认证器:public function behaviors() { $behaviors = parent::behaviors(); $behaviors['authenticator'] = [ 'class' => HttpBearerAuth::className(), // 声明认证器 ]; return $behaviors; }
在子类控制器,继承
BaseController
声明哪些动作不需要认证:public function behaviors() { $behaviors = parent::behaviors(); $behaviors['authenticator']['optional'] = ['action1', 'action2']; // 不需要认证的action return $behaviors; }
总结
笔者通过这个体会到接口的意义,User
类实现IdentityInterface
,就要实现其findIdentityByAccessToken
()方法,否则报错:接口方法没实现。为什么要设定为报错,为什么要有 实现了接口,就一定要实现其方法的规定?因为,很有可能,接口方法在某个地方被调用了!,进而引出对面向对象接口的理解,有兴趣的可跳转:面向对象的接口使用前人代码的方式