上次搭建了Yii2.0的接口框架后,现在开始搭建认证和限流模块,先说下这两个模块的作用
认证:前后端分离,每次请求都是无状态的,及每一次请求服务器不知道你是谁,你有没有登陆;我们就需要做一个认证模块去识别请求用户;
限流:为了防止接口滥用,我们可以设置这个接口,每个用户每秒钟的访问次数;
因为限流是在认证的基础上,所以我们先来说认证:流程就是我们从服务器请求获取token,然后每次请求都带着token,当然请求token肯定需要输入用户名密码或者密钥之类的,这里采用用户名密码;另外,token需要配置过期时间,过期了需要重新获取
main中配置认证类,这里我使用的认证类是operator,对应数据库中的operator表
'components'=>[ 'user' => [ 'identityClass' => 'app\modules\v1\models\Operators', 'enableAutoLogin' => true, //禁用session组件 'enableSession'=>false, ], ]
2. 配置这个认证类,继承IdentityInterface这个类,数据表中增加access_token和expire_time这两个字段,想要的rules()和attributeLabels()方法中同样配置这两个字段,重写以下方法
public static function findByUsername($username = null) { return static::findOne(['operator_name' => $username]); } public function validatePassword($inputPwd, $pwd) { return Yii::$app->getSecurity()->validatePassword($inputPwd, $pwd) ? true : false; } public static function findIdentity($id) { // TODO: Implement findIdentity() method. return static::findOne($id); } public static function findIdentityByAccessToken($token, $type = null) { // TODO: Implement findIdentityByAccessToken() method. return static::findOne(['access_token' => $token]); } public function getId() { // TODO: Implement getId() method. return $this->id; } public function getAuthKey() { // TODO: Implement getAuthKey() method. return $this->auth_key; } public function validateAuthKey($authKey) { // TODO: Implement validateAuthKey() method. } /** * 生成随机的token并加上时间戳 * Generated random accessToken with timestamp * @throws \yii\base\Exception */ public function generateAccessToken() { $this->access_token = Yii::$app->security->generateRandomString(); $this->expire_time = time(); } /** * 验证token是否过期 * Validates if accessToken expired * @param null $token * @return bool */ public static function validateAccessToken($token = null,$timestamp=0) { if ($token === null) { return false; } else { $expire = Yii::$app->params['accessTokenExpire']; return $timestamp + $expire >= time(); } }
3.既然需要获取token,我们需要一个loginForm,注意,这里的login方法和之前的login方法不一样
<?php namespace app\modules\v1\models; use Yii; use yii\base\Model; class LoginForm extends Model { public $userName; public $password; private $_user = false; const GET_ACCESS_TOKEN = 'generate_access_token'; public function init() { parent::init(); $this->on(self::GET_ACCESS_TOKEN,[$this, 'onGenerateAccessToken']); } public function rules() { return [ [['userName', 'password'], 'required'], ['password', 'validatePassword'], ]; } public function attributeLabels() { return [ 'userName' => '用户名', 'password' => '密码', ]; } public function validatePassword($attribute) { if (!$this->hasErrors()) { $user = $this->getUser(); if (!$user || (!$user->validatePassword($this->password,$user->password))) { $this->addError($attribute, '用户名/密码错误'); } } } public function login() { if ($this->validate()) { $this->_user = $this->getUser(); //如果token过期或者为空,直接生成新token if(!Operators::validateAccessToken($this->_user->access_token,$this->_user->expire_time)){ $this->trigger(self::GET_ACCESS_TOKEN); } return Yii::$app->user->login($this->getUser(),0); } return false; } public function getUser() { if ($this->_user === false) { $this->_user = Operators::findByUsername($this->userName); } return $this->_user; } /** * 当登录成功时更新用户的token * Generated new accessToken when validate successful. * If accessToken is invalid, generated a new token for it. * @throws \yii\base\Exception */ public function onGenerateAccessToken() { if (!Operators::validateAccessToken($this->getUser()->access_token,$this->getUser()->expire_time)) { $this->getUser()->generateAccessToken(); $this->getUser()->save(false); } } }
4.我们新建接口方法去调用loginForm中的login()方法去生成或者返回token,这里要注意,不能用load,需要使用setAttributes
class LoginAction extends BaseAction { public function run(){ $model = new LoginForm(); $model->setAttributes($this->body); if ($model->login()) { $code = 0; $result['code'] = $code; $result['desc'] = $this->desc[$code]; $result['data']['token'] = $model->user->access_token; return $result; } $code = 40010; $result['code'] = $code; $result['desc'] = $this->desc[$code]; $result['data'] = []; return $result; } }
另外,接口的基类控制器中的behaviors中需要加一下代码,yii框架会自动认证
//认证,验证 $behaviors['authenticator'] = [ 'class' => CompositeAuth::className(), 'authMethods' => [ HttpBasicAuth::className(), HttpBearerAuth::className(), QueryParamAuth::className(), ], ];
这里可以请求下v1/operator/login方法,并传递用户名和密码,得到token值
然后我们拿着这个token值去调用其它的接口,如果不传递或者传递错误,会返回401错误(无权限)
到此认证完成,其实步骤多了点,一看代码就明白了,文章最后有github地址
然后我们来说限流,限流这块yii文档中,说的特别简单(其实本来就很简单)
首先给operator这个表增加两个字段,allowance(剩余访问次数)和allowance_updated_at(访问时间)
operator继承这个类RateLimitInterface,并重写以下方法
public function getRateLimit($request, $action) { return [1, 1]; // 前面的1是次数,后面的1是秒数 } public function loadAllowance($request, $action) { return [$this->allowance, $this->allowance_updated_at]; } public function saveAllowance($request, $action, $allowance, $timestamp) { $this->allowance = $allowance; $this->allowance_updated_at = $timestamp; $this->save(); }
3.我们需要在接口的基类baseController的behaviors中增加以下代码,behaviors的作用就是对类进行功能的扩充,behaviors的作用就是对类进行功能的扩充,behaviors的作用就是对类进行功能的扩充 ,不懂的同学请看我之前写的文章,传送门
//速率限制 $behaviors['rateLimiter'] = [ 'class' => RateLimiter::className(), 'enableRateLimitHeaders' => true, ];
然后就可以啦,我设置的是1s访问一次
连续请求频繁后,我们发现,接口会返回429错误码(请求频繁)