Yii2 restful api创建,认证授权以及速率控制
Yii2 restful api创建,认证授权以及速率控制
下面是对restful从创建到速率控制的一个详细流程介绍,里面的步骤以及截图尽可能详细,熟悉restful的盆友可能觉得过于繁琐,新手不妨耐心仔细看一下。
一.Api的创建
1. 复制一个frontend或者backend,将其重命名为api放在同级目录下
2. 然后删除controllers和views文件夹,然后将api文件中的frontend替换为api(比如命名空间,相关配置等),这点非常重要!!!
3. 打开cmd命令行,cd进入项目所在根目录C:\wamp\www\advanced下,init 根据提示进行初始化设置,之后修改common\config\main-local.php文件中的数据库相关配置。这个比较简单。
4. 需要修改common\config\bootstrap.php文件,对新建的应用增加alias别名,如下:
Yii::setAlias('@api', dirname(dirname(__DIR__)) . '/api');
注意:增加api别名很重要,否则使用gii生成相应文件的时候,会报错,找不到相应的命名空间
5.为新建的api应用程序美化路由,为了后续方便访问,进行域名配置
1)首先保证你的web服务器开启rewrite规则,细节我们就不说了,不过这是前提。
2)给api目录配置一个访问地址:www.dsgn.com指向 api/web,不会的盆友请参考我的另一篇博客,关于Apache的域名配置
3)接着,在应用入口同级增加.htaccess文件就好,我们以apache为例
.htaccess文件内容:
1 Options +FollowSymLinks 2 3 IndexIgnore */* 4 5 RewriteEngine on 6 7 # if a directory or a file exists, use it directly 8 9 RewriteCond %{REQUEST_FILENAME} !-f 10 11 RewriteCond %{REQUEST_FILENAME} !-d 12 13 # otherwise forward it to index.php 14 15 RewriteRule . index.php 16 17 RewriteRule \.svn\/ /404.html 18 19 RewriteRule \.git\/ /404.html
6.用了便于演示说明,我们新建一张数据表goods表,字段自己随意设置,并向其中插入几条数据。
1 CREATE TABLE `goods` ( 2 `id` int(11) NOT NULL AUTO_INCREMENT, 3 `name` varchar(100) NOT NULL DEFAULT '', 4 PRIMARY KEY (`id`) 5 ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 6 7 INSERT INTO `goods` VALUES ('1', '11111'); 8 INSERT INTO `goods` VALUES ('2', '22222'); 9 INSERT INTO `goods` VALUES ('3', '333'); 10 INSERT INTO `goods` VALUES ('4', '444'); 11 INSERT INTO `goods` VALUES ('5', '555');
7. 通过gii生成modules
注意:由于刚才配置的域名是访问到api/web,现在gii生成器的地址我用localhost去访问fronted/web/index.php
地址:http://localhost/advanced/frontend/web/index.php?r=gii
然后依次生成模块 控制器 模型
生成模型Model类:
8.同时在 api/config/main.php 中做如下配置,这一步也很重要
1)添加module的配置
1 'modules' => [ 2 3 'v1' => [ 4 5 'class' => 'api\modules\v1\Module', 6 7 ], 8 9 ],
2)修改controllerNamespace
1 'controllerNamespace' => 'api\controllers'
3)开启urlManager,如下配置
1 'urlManager' => [ 2 'enablePrettyUrl' => true, 3 'enableStrictParsing' => true, 4 'showScriptName' => false, 5 // 为Goods配置Url规则 6 'rules' => [ 7 ['class' => 'yii\rest\UrlRule', 'controller' => ['v1/goods']], 8 ], 9 ],
4)删除
'errorHandler' => [
'errorAction' => 'site/error',
],
9. 重新配置控制器。为了实现restful风格的api,在yii2中,我们需要对 api\modules\v1\controllers下的GoodsController控制器进行一下改写
1 <?php 2 3 namespace api\modules\v1\controllers; 4 5 use yii\rest\ActiveController; 6 7 class GoodController extends ActiveController 8 9 { 10 public $modelClass = 'api\models\Goods'; 11 }
说明:翻阅资料的时候,看到有人提出,在这个控制器里面自定义方法,但是访问的时候报404
原因如下:RESTful是以资源为中心而不是页面,所以YII2给每个资源只给了有限的几个action,而且是以Action类的形式写的,
它默认有create, delete, update, index, view等的一下方法
所创建的 API 包括:
1 GET /users: 逐页列出所有用户 2 POST /users: 创建一个新用户 3 GET /users/123: 返回用户 123 的详细信息 4 PATCH /users/123 and PUT /users/123: 更新用户123 5 DELETE /users/123: 删除用户123
1)框架默认的action都写在yii\rest包下(XXXAction.php就表示相应的动词操作,源代码很少,你可以直接看看),
2)如果这个资源的所有动词都是默认的话是则它的controller里除了modelClass是不用写任何代码的.
3)如果想要自定义动词必须继承rest包下对应的Action类,然后在controller重写actions指向你的新action,从而实现重写
打开文件api\modules\v1\controllers\UserController,参考代码如下:
如果我们想在自己定义不同的api接口的话?那么我们可以通过配置实现,在main.php的主文件中:
1 'urlManager' => [ 2 'enablePrettyUrl' => true, 3 'showScriptName' => false, 4 'rules' => [ 5 [ 6 'class' => 'yii\rest\UrlRule', 7 'controller' => ['v1/users'], 8 'pluralize' => false, 9 'extraPatterns' => [ 10 'GET versions' => 'version', 11 'GET search/<id:\d+>' => 'search', 12 'POST newusers' => 'add' 13 ], 14 ], 15 ], 16 ],
"extraPatterns"这个属性是额外模式配置
1 'GET versions' => 'version',代表获取接口版本,例如/v1/users/versions ,对应的内联操作actionVersion(); 2 3 'GET searches/<id:\d+>' => 'search', 代表搜索一个指定id的用户,例如/v1/users/searches/1,对应的内联操作actionSearches(); 4 5 'POST newusers' => 'add',代表添加一个用户,例如/v1/users/newusers,对应的内联操作actionAdd();
我们只需要在控制器中实现这些方法,完成相应的逻辑业务,那么各个需要的接口就完成了。
9. 模拟请求操作
到这一步就可以用postman来测试,不会安装或者调试的盆友请网上搜索一下,当然也可以用其他测试工具来调试。
从上面截图中可以清楚的看到,GET 方式请求http://www.dsgn.com/v1/goods, 已经能够很方便的获取我们表中的数据了。
补充说明:
1)Yii 将在末端使用的控制器的名称自动变为复数。这个要重点注意!
这个地方可能还不明显,因为定义的表名就是goods(商品表)。比如 user用户表,这个地方就会自动变为 v1/users
2)这个地方使用之所以使用复数,是因为在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。
一般来说,数据库中的表都是同种记录的"集合"(collection),所以API中的名词也应该使用复数。
3)当然,用yii2的restful写api,暂时可以不用框架推荐的yii\rest\UrlRule,就可以写出一般路由的api了,主要因为yii\rest\UrlRule的路由简略了很多,不利于直接识别api代表的意思。
例子:
使用yii\rest\UrlRule,访问链接为/profiles/3
使用一般URL规则'<controller:\w+>/<action:\w+>' => '<controller>/<action>', 访问链接为/profile/view?id=3
测试结果都一致。
httpx://api.yii.com/profile/view?id=1&expand=user
yii的restful要获得关联表数据必须要带一个expand参数表明需要使用的关联数据
4)如果是用restful自带的路由规则,要解决访问存在的复数问题的话,需要添加'pluralize' => false这个条件,具体如下:
1 'rules' => [ 2 [ 3 'class' => 'yii\rest\UrlRule', 4 'controller' => ['v1/user'], 5 // 不使用复数 6 'pluralize' => false 7 ], 8 ],
到此为止,用http://www.dsgn.com/v1/user就能成功测试访问了。
但是,有个问题出现了,用postman测试的数据,可以看到是json格式的,那是因为postman测试工具可以自动转化成json美化格式,
实际中我们用浏览器访问该url看到如下,(这跟是否用复数访问没关系)
解决方法是,在GoodController控制器下重写behaviors函数,具体做法是加这样一段代码:
1 public function behaviors() 2 3 { 4 5 $behaviors=parent::behaviors(); 6 7 $behaviors['contentNegotiator']['formats'] = [ 8 9 'application/json'=>Response::FORMAT_JSON 10 11 ]; 12 13 return $behaviors; 14 15 }
说明:Yii2 RESTful支持JSON和XML格式,如果想指定返回数据的格式,需要配置yii\filters\ContentNegotiator::formats属性
现在重新去访问http://www.dsgn.com/v1/good就可以看到以json格式解析出来了。O(∩_∩)O~~~
补充说明:
API的目录结构有2种方式:
之前的创建测试都是在方式1的情况下进行的,其实这两种方式的差异并不大,现在对方式2进行补充说明:
打开api\config\main.php中,有如下配置:
二、认证授权
1.需要设置认证的原因:和Web应用不同,RESTful APIs 通常是无状态的,也就意味着不应使用sessions 或 cookies,因此每个请求应附带某种授权凭证,因为用户授权状态可能没通过sessions 或 cookies维护,
常用的做法是每个请求都发送一个秘密的access token来认证用户,由于access token可以唯一识别和认证用户, API 请求应通过HTTPS来防止man-in-the-middle (MitM) 中间人攻击.
2.准备工作:首先,要创建一个user表,可以用yii2自带的migrate创建,不会的请查阅相关资料。或者用sql语句建表,如下:
1 CREATE TABLE `user` ( 2 `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 3 `username` varchar(20) NOT NULL DEFAULT '' COMMENT '用户名', 4 `password_hash` varchar(100) NOT NULL DEFAULT '' COMMENT '密码', 5 `password_reset_token` varchar(50) NOT NULL DEFAULT '' COMMENT '密码token', 6 `email` varchar(20) NOT NULL DEFAULT '' COMMENT '邮箱', 7 `auth_key` varchar(50) NOT NULL DEFAULT '', 8 `status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '状态', 9 `created_at` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', 10 `updated_at` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', 11 `access_token` varchar(50) NOT NULL DEFAULT '' COMMENT 'restful请求token', 12 PRIMARY KEY (`id`), 13 UNIQUE KEY `username` (`username`), 14 UNIQUE KEY `access_token` (`access_token`) 15 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
插入一条或者几条数据,以便后期测试
3.配置user应用组件(不是必要的,但是推荐配置):
说明:1) 设置yii\web\User::enableSession属性为false(因为RESTful APIs为无状态的,当yii\web\User::enableSession为false,请求中的用户认证状态就不能通过session来保持)
2) 设置yii\web\User::loginUrl属性为null(显示一个HTTP 403 错误而不是跳转到登录界面)
3) 'identityClass' => 'api\models\User',
指定认证的model类,现在是在api\models\User这个类中,那么我们需要在api\models\User这个类中实现,yii\web\IdentityInterface这个类中的所有定义的接口方法
1 'user' => [ 2 3 'identityClass' => 'api\models\User', 4 5 'enableAutoLogin' => true, 6 7 'enableSession'=>false, 8 9 'identityCookie' => ['name' => '_identity-api', 'httpOnly' => true], 10 11 ],
4.为控制器配置authenticator行为指定认证方式
1 <?php 2 3 namespace api\modules\v1\controllers; 4 5 use yii\rest\ActiveController; 6 7 use yii\helpers\ArrayHelper; 8 9 use yii\filters\auth\QueryParamAuth; 10 11 class UserController extends ActiveController 12 13 { 14 15 public $modelClass = 'api\models\User'; 16 17 18 19 public function behaviors() { 20 21 $behaviors = parent::behaviors(); 22 23 $behaviors['authenticator'] = [ 24 25 // 注意:此处如果是在apache环境下采用HttpBearerAuth认证方式,需要在api/web下的.htaccess中加上一句:SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1 26 27 'class' => QueryParamAuth::className(), 28 29 ]; 30 31 $behaviors['contentNegotiator']['formats'] = [ 32 33 'application/json'=> Response::FORMAT_JSON 34 35 ]; 36 37 return $behaviors; 38 39 } 40 41 }
说明:behaviors()这个函数的是定义这个控制器类的行为,也就是每一次访问这个控制器的方法,
都会执行这个behaviors中定义的各种行为,认证也是这个流程,我们访问一个api接口时,
就会执行yii\filters\auth\QueryParamAuth的这个文件的authenticate()这个方法
5.在api/config/main.php中
我们需要在配置文件中保证,步骤3配置user组件中曾提到过,这里作为补充说明。
'user' => [
'identityClass' => 'api\models\User',
],
这里是指定认证的model类,现在是在api\models\User这个类中,那么我们需要在api\models\User这个类中实现yii\web\IdentityInterface这个类中的所有定义的接口方法,在api/models/User.php中代码如下:
1 <?php 2 3 namespace api\models; 4 5 use yii\db\ActiveRecord; 6 7 use yii\web\IdentityInterface; 8 9 class User extends ActiveRecord implements IdentityInterface{ 10 11 public static function tableName() 12 13 { 14 return 'user'; 15 } 16 17 public function rules() 18 19 { 20 return [ 21 22 [['username', 'auth_key', 'password_hash', 'email', 'created_at', 'updated_at'], 'required'], 23 24 [['status', 'created_at', 'updated_at', 'allowance', 'allowance_updated_at'], 'integer'], 25 26 [['username', 'password_hash', 'password_reset_token', 'email'], 'string', 'max' => 255], 27 28 [['auth_key'], 'string', 'max' => 32], 29 30 [['access_token'], 'string', 'max' => 60], 31 32 [['username'], 'unique'], 33 34 [['email'], 'unique'], 35 36 [['password_reset_token'], 'unique'], 37 38 ]; 39 40 } 41 42 public function attributeLabels() 43 44 { 45 46 return [ 47 48 'id' => 'ID', 49 50 'username' => 'Username', 51 52 'auth_key' => 'Auth Key', 53 54 'password_hash' => 'Password Hash', 55 56 'password_reset_token' => 'Password Reset Token', 57 58 'email' => 'Email', 59 60 'status' => 'Status', 61 62 'created_at' => 'Created At', 63 64 'updated_at' => 'Updated At', 65 66 'access_token' => 'Access Token', 67 68 'allowance' => 'Allowance', 69 70 'allowance_updated_at' => 'Allowance Updated At', 71 72 ]; 73 74 } 75 76 //实现接口里的全部方法 77 78 public static function findIdentity($id) 79 80 { 81 82 return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]); 83 84 } 85 86 public static function findIdentityByAccessToken($token, $type = null) 87 88 { 89 return static::findOne(['access_token' => $token]); //数据库user表中的字段access_token 90 } 91 92 //这个就是我们进行yii\filters\auth\QueryParamAuth调用认证的函数,下面会说到。 93 94 95 /*=======用Yii提供的认证方式验证token时需要自定义的函数:一定要实现,否则会报错=======*/ 96 97 public function loginByAccessToken($accessToken, $type) { 98 99 //查询数据库中有没有存在这个token 100 101 return static::findIdentityByAccessToken($accessToken, $type); 102 103 } 104 105 public static function findByUsername($username) 106 107 { 108 109 return static::findOne(['username' => $username, 'status' => self::STATUS_ACTIVE]); 110 111 } 112 113 public function getId() 114 115 { 116 return $this->getPrimaryKey(); 117 } 118 119 public function getAuthKey() 120 121 { 122 return $this->auth_key; 123 } 124 125 public function validateAuthKey($authKey) 126 { 127 return $this->getAuthKey() === $authKey; 128 } 129 130 }
这样一来,整个认证的过程就完成了。
现在对数据库user表中的access_token和访问时需携带的参数access-token进行简单说明:
1.user表中的参数access_token作用:
在实现IdentityInterface接口类,重写findIdentityByAccessToken()方法中,根据access_token校验用户身份,获取用户信息
2.url访问时,通过携带的参数access-token作用:
利用该参数进行认证。此参数其实是对yii\filters\auth\QueryParanAuth中的属性$tokenParam设置的值。
细心的盆友会发现,这其实是UserController中行为中配置authenticator行为对应的认证类。
$tokenParam这个属性是设置url附带的token的参数key,我们可以在behaviors()这个函数中配置修改:
更改之后url附带的参数key就应该是token.
4. 如此一来,我们先通过postman模拟不带access-token请求看结果
提示401 我们没有权限访问!
5. 我们在请求的链接上携带正确的access-token,认证通过后,控制器会再继续执行其他检查(频率限制、操作权限等),才可以返回正确的用户信息。
如果携带access-token在user表中找不到与该字段对应的值,那么结果跟步骤4中显示的还是一样。这里访问http://www.dsgn.com/v1/user?access-token=123456,进行测试:
如果此处要指定哪些字段放在结果中,可以使用fields或者expand参数,如:http://www.dsgn.com/v1/user?fields=id,username&access-token=123456,这样GET的结果就只显示包含id,username这2个字段的结果了
6.授权:在用户认证通过后,你可能想检查他是否有足够的权限来访问请求资源的这个动作, 那么就在api/modules/controllers下对应的控制器中重写函数checkAccess
public function checkAccess($action, $model = null, $params = [])
{
}
三、速率控制
说明:为防止滥用,可以增加速率限制。例如,限制每个用户的API的使用是在60秒内最多5次的API调用,
如果一个用户同一个时间段内太多的请求被接收,将返回响应状态代码 429 (这意味着过多的请求)。
1.在我们启用了速率限制后,Yii会自动使用yii\filters\RateLimiter为yii\rest\Controller配置一个行为过滤器来执行速率限制检查。
如果速度超出限制,该速率限制器将抛出一个yii\web\TooManyRequestsHttpException。
2.给user表添加2个字段
`allowance` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'restful剩余的允许的请求数',
`allowance_updated_at` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'restful请求的UNIX时间戳数',
3.在api相应的控制器下,behaviors函数下添加以下代码:
1 $behaviors['rateLimiter'] = [ 2 3 'class' => RateLimiter::className(), 4 5 'enableRateLimitHeaders' => true, 6 7 ];
3.在user表中使用两列来记录容差和时间戳信息。为了提高性能,可以考虑使用缓存或NoSQL存储这些信息。修改api/models/User.php,改动或者加入以下代码:
1 <?php 2 use yii\filters\RateLimitInterface; 3 4 class User extends ActiveRecord implements IdentityInterface,RateLimitInterface 5 { 6 //限制同一接口某时间段过多的请求,需要实现yii\filters\RateLimitInterface接口的全部方法==================== 7 8 // 返回某一时间允许请求的最大数量,比如设置2秒内最多1次请求(小数量方便我们模拟测试) 9 // 注意:这个地方建议写成几秒1次,如果写成几秒几次,那么很可能在1秒之内就将限制的总次数用完了,这样就失去了限流的意义 10 public function getRateLimit($request, $action){ 11 12 return [1, 2]; 13 14 } 15 16 // 返回剩余的允许的请求和相应的UNIX时间戳数 当最后一次速率限制检查时 17 18 public function loadAllowance($request, $action){ 19 20 return [$this->allowance, $this->allowance_updated_at]; 21 22 } 23 24 // 保存允许剩余的请求数和当前的UNIX时间戳 25 26 public function saveAllowance($request, $action, $allowance, $timestamp){ 27 28 $this->allowance = $allowance; 29 30 $this->allowance_updated_at = $timestamp; 31 32 $this->save(); 33 34 }
35 }
4.通过postman请求http://www.dsgn.com/v1/user?access-token=12345,通过测试结果发现,60秒内调用超过5次API接口,我们会得到状态为429太多请求的异常信息。
注意:以下红框里面的头信息内容,可以通过在api/models/User.php中,通过设置禁用掉
5.错误处理
Yii的REST框架的HTTP状态代码可参考如下:
200: OK。一切正常。
201: 响应 POST 请求时成功创建一个资源。Location header 包含的URL指向新创建的资源。
204: 该请求被成功处理,响应不包含正文内容 (类似 DELETE 请求)。
304: 资源没有被修改。可以使用缓存的版本。
400: 错误的请求。可能通过用户方面的多种原因引起的,例如在请求体内有无效的JSON 数据,无效的操作参数,等等。
401: 验证失败。
403: 已经经过身份验证的用户不允许访问指定的 API 末端。
404: 所请求的资源不存在。
405: 不被允许的方法。 请检查 Allow header 允许的HTTP方法。
415: 不支持的媒体类型。 所请求的内容类型或版本号是无效的。
422: 数据验证失败 (例如,响应一个 POST 请求)。 请检查响应体内详细的错误消息。
429: 请求过多。 由于限速请求被拒绝。
500: 内部服务器错误。 这可能是由于内部程序错误引起的。