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: 内部服务器错误。 这可能是由于内部程序错误引起的。

posted @ 2017-01-13 17:43  欢乐豆123  阅读(1093)  评论(2编辑  收藏  举报