Yii2 restful api创建,认证授权以及速率控制

Yii2 restful api创建,认证授权以及速率控制

下面是对restful从创建到速率控制的一个详细流程介绍,里面的步骤以及截图尽可能详细,熟悉restful的盆友可能觉得过于繁琐,新手不妨耐心仔细看一下。

一.Api的创建

1.复制一个frontend或者backend,将其重命名为api放在同级目录下

 

2.然后删除controllers和views文件夹,然后将api文件中的frontend替换为api(比如命名空间,相关配置等),这点非常重要!!!

 

 

3.打开cmd命令行,cd进入项目所在根目录C:wampwwwadvanced下,init 根据提示进行初始化设置,之后修改commonconfigmain-local.php文件中的数据库相关配置。这个比较简单。

4.需要修改commonconfigootstrap.php文件,对新建的应用增加alias别名,如下:  

Yii::setAlias('@api', dirname(dirname(__DIR__)) . '/api');

5.为新建的api应用程序美化路由,为了后续方便访问,进行域名配置

  1)首先保证你的web服务器开启rewrite规则,细节我们就不说了,不过这是前提。

  2)给api目录配置一个访问地址:www.dsgn.com指向 api/web,不会的盆友请参考我的另一篇博客,关于Apache的域名配置

  3)接着,在应用入口同级增加.htaccess文件就好,我们以apache为例

 

.htaccess文件内容:

Options +FollowSymLinks

IndexIgnore */*

RewriteEngine on

# if a directory or a file exists, use it directly

RewriteCond %{REQUEST_FILENAME} !-f

RewriteCond %{REQUEST_FILENAME} !-d

# otherwise forward it to index.php

RewriteRule . index.php

RewriteRule .svn/  /404.html

RewriteRule .git/  /404.html

6.用了便于演示说明,我们新建一张数据表good表,字段自己随意设置,并向其中插入几条数据。

 

CREATE TABLE `goods` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 

INSERT INTO `goods` VALUES ('1', '11111');
INSERT INTO `goods` VALUES ('2', '22222');
INSERT INTO `goods` VALUES ('3', '333');
INSERT INTO `goods` VALUES ('4', '444');
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

 

 

 

然后依次生成模块 控制器 模型

 

 

 

8.同时在 api/config/main.php 中做如下配置,这一步也很重要

  1)添加module的配置

     'modules' => [

        'v1' => [

            'class' => 'apimodulesv1Module',

        ],

    ],

  2)修改controllerNamespace

    'controllerNamespace' => 'apicontrollers',

  3)开启urlManager,如下配置

'urlManager' => [

    'enablePrettyUrl' => true,

    'enableStrictParsing' => true,

    'showScriptName' => false,

 //为Goods配置Url规则

    'rules' => [

        ['class' => 'yii estUrlRule', 'controller' => ['v1/good']],

    ],

],

 4)删除

   'errorHandler' => [

        'errorAction' => 'site/error',

   ],

 9. 重新配置控制器。为了实现restful风格的api,在yii2中,我们需要对 apimodulesv1controllers下的GoodController控制器进行一下改写

 <?php

namespace apimodulesv1controllers;

 

use yii estActiveController;

 

class GoodController extends ActiveController

{

        public $modelClass = 'apimodelsGood';

}

 

说明:翻阅资料的时候,看到有人提出,在这个控制器里面自定义方法,但是访问的时候报404

原因如下:RESTful是以资源为中心而不是页面,所以YII2给每个资源只给了有限的几个action,而且是以Action类的形式写的,

它默认有create, delete, update, index, view等的一下方法
所创建的 API 包括:
GET /users: 逐页列出所有用户
POST /users: 创建一个新用户
GET /users/123: 返回用户 123 的详细信息
PATCH /users/123 and PUT /users/123: 更新用户123
DELETE /users/123: 删除用户123

1)框架默认的action都写在yii est包下(XXXAction.php就表示相应的动词操作,源代码很少,你可以直接看看),

2)如果这个资源的所有动词都是默认的话是则它的controller里除了modelClass是不用写任何代码的.

3)如果想要自定义动词必须继承rest包下对应的Action类,然后在controller重写actions指向你的新action,从而实现重写

打开文件apimodulesv1controllersUserController,参考代码如下:

 

如果我们想在自己定义不同的api接口的话?那么我们可以通过配置实现,在main.php的主文件中:
'urlManager' => [
'enablePrettyUrl' => true,
'showScriptName' => false,
'rules' => [
[
'class' => 'yii estUrlRule',
'controller' => ['v1/users'],
'pluralize' => false,
'extraPatterns' => [
'GET versions' => 'version',
'GET search/<id:d+>' => 'search',
'POST newusers' => 'add'
],

],
],
],
"extraPatterns"这个属性是额外模式配置
a)'GET versions' => 'version',代表获取接口版本,例如/v1/users/versions ,对应的内联操作actionVersion();

 


b)'GET searches/<id:d+>' => 'search', 代表搜索一个指定id的用户,例如/v1/users/searches/1,对应的内联操作actionSearches();

 

 

 

c)'POST newusers' => 'add',代表添加一个用户,例如/v1/users/newusers,对应的内联操作actionAdd();

 

我们只需要在控制器中实现这些方法,完成相应的逻辑业务,那么各个需要的接口就完成了。

 

9.模拟请求操作

到这一步就可以用postman来测试,不会安装或者调试的盆友请网上搜索一下,当然也可以用其他测试工具来调试。

 

从上面截图中可以清楚的看到,GET 方式请求http://www.dsgn.com/v1/goods, 已经能够很方便的获取我们表中的数据了。

补充说明:1)Yii 将在末端使用的控制器的名称自动变为复数。这个要重点注意!

2)这个地方使用之所以使用goods这个复数,是因为在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。

一般来说,数据库中的表都是同种记录的"集合"(collection,所以API中的名词也应该使用复数。

3)当然,用yii2的restful写api,暂时可以不用框架推荐的yii estUrlRule,就可以写出一般路由的api了,主要因为yii estUrlRule的路由简略了很多,不利于直接识别api代表的意思。

例子:

使用yii estUrlRule,访问链接为/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这个条件,具体如下:

 'rules' => [

                  ['class' => 'yii estUrlRule',

                   'controller' => ['v1/good'],

                   'pluralize' => false

],

            ],

到此为止,用http://www.dsgn.com/v1/good就能成功测试访问了。

但是,有个问题出现了,用postman测试的数据,可以看到是json格式的,那是因为postman测试工具可以自动转化成json美化格式,

实际中我们用浏览器访问该url看到如下,(这跟是否用复数访问没关系)

 

解决方法是,在GoodController控制器下重写behaviors函数,具体做法是加这样一段代码:

 public function behaviors()

{

     $behaviors=parent::behaviors();

     $behaviors['contentNegotiator']['formats']  =  [

            'application/json'=>Response::FORMAT_JSON

     ];      

     return $behaviors;

}

说明:Yii2 RESTful支持JSON和XML格式,如果想指定返回数据的格式,需要配置yiifiltersContentNegotiator::formats属性

现在重新去访问http://www.dsgn.com/v1/good就可以看到以json格式解析出来了。O(∩_∩)O~~~

补充说明:

API的目录结构有2种方式

之前的创建测试都是在方式1的情况下进行的,其实这两种方式的差异并不大,现在对方式2进行补充说明:

打开apiconfigmain.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语句建表,如下

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (

`id` int(10) unsigned NOT NULL AUTO_INCREMENT,

`username` varchar(20) NOT NULL DEFAULT '' COMMENT '用户名',

`password_hash` varchar(100) NOT NULL DEFAULT '' COMMENT '密码',

`password_reset_token` varchar(50) NOT NULL DEFAULT '' COMMENT '密码token',

`email` varchar(20) NOT NULL DEFAULT '' COMMENT '邮箱',

`auth_key` varchar(50) NOT NULL DEFAULT '',

`status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '状态',

`created_at` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',

`updated_at` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',

`access_token` varchar(50) NOT NULL DEFAULT '' COMMENT 'restful请求token',

PRIMARY KEY (`id`),

UNIQUE KEY `username` (`username`),

UNIQUE KEY `access_token` (`access_token`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

插入一条或者几条数据,以便后期测试

3.配置user应用组件(不是必要的,但是推荐配置):

说明:1) 设置yiiwebUser::enableSession属性为false(因为RESTful APIs为无状态的,当yiiwebUser::enableSession为false,请求中的用户认证状态就不能通过session来保持)

    2) 设置yiiwebUser::loginUrl属性为null(显示一个HTTP 403 错误而不是跳转到登录界面)

          3) 'identityClass' => 'apimodelsUser',

  指定认证的model类,现在是在apimodelsUser这个类中,那么我们需要在apimodelsUser这个类中实现,yiiwebIdentityInterface这个类中的所有定义的接口方法

    'user' => [

            'identityClass' => 'apimodelsUser',

            'enableAutoLogin' => true,

            'enableSession'=>false,

            'identityCookie' => ['name' => '_identity-api', 'httpOnly' => true],

        ],

4.为控制器配置authenticator行为指定认证方式

<?php

namespace apimodulesv1controllers;

use yii estActiveController;

use yiihelpersArrayHelper;

use yiifiltersauthQueryParamAuth;

class UserController extends ActiveController

{

    public $modelClass = 'apimodelsUser';

 

    public function behaviors()  {

        $behaviors = parent::behaviors();

        $behaviors['authenticator'] = [

        //注意:此处如果是在apache环境下采用HttpBearerAuth认证方式,需要在api/web下的.htaccess中加上一句:SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1

                'class' => QueryParamAuth::className(),

        ];

       $behaviors['contentNegotiator']['formats']  =  [

              'application/json'=> Response::FORMAT_JSON

        ];

        return $behaviors;

    }

}

说明:behaviors()这个函数的是定义这个控制器类的行为,也就是每一次访问这个控制器的方法,

都会执行这个behaviors中定义的各种行为,认证也是这个流程,我们访问一个api接口时,

就会执行yiifiltersauthQueryParamAuth的这个文件的authenticate()这个方法

5.在api/config/main.php中

我们需要在配置文件中保证,步骤3配置user组件中曾提到过,这里作为补充说明。

'user' => [

    'identityClass' => 'apimodelsUser',

],

这里是指定认证的model类,现在是在apimodelsUser这个类中,那么我们需要在apimodelsUser这个类中实现yiiwebIdentityInterface这个类中的所有定义的接口方法,在api/models/User.php中代码如下:

<?php

namespace apimodels;

use yiidbActiveRecord;

use yiiwebIdentityInterface;

class User extends ActiveRecord implements  IdentityInterface{

    public static function tableName()

    {

        return 'user';

    }

    public function rules()

    {

        return [

            [['username', 'auth_key', 'password_hash', 'email', 'created_at', 'updated_at'], 'required'],

            [['status', 'created_at', 'updated_at', 'allowance', 'allowance_updated_at'], 'integer'],

            [['username', 'password_hash', 'password_reset_token', 'email'], 'string', 'max' => 255],

            [['auth_key'], 'string', 'max' => 32],

            [['access_token'], 'string', 'max' => 60],

            [['username'], 'unique'],

            [['email'], 'unique'],

            [['password_reset_token'], 'unique'],

        ];

    }

    public function attributeLabels()

    {

        return [

            'id' => 'ID',

            'username' => 'Username',

            'auth_key' => 'Auth Key',

            'password_hash' => 'Password Hash',

            'password_reset_token' => 'Password Reset Token',

            'email' => 'Email',

            'status' => 'Status',

            'created_at' => 'Created At',

            'updated_at' => 'Updated At',

            'access_token' => 'Access Token',

            'allowance' => 'Allowance',

            'allowance_updated_at' => 'Allowance Updated At',

        ];

    }

    //实现接口里的全部方法

    public static function findIdentity($id)

    {

        return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]);

    }

    public static function findIdentityByAccessToken($token, $type = null)

    {

        return static::findOne(['access_token' => $token]);       //数据库user表中的字段access_token

    }

    //这个就是我们进行yiifiltersauthQueryParamAuth调用认证的函数,下面会说到。

 

 

    /*=======用Yii提供的认证方式验证token时需要自定义的函数:一定要实现,否则会报错=======*/

    public function loginByAccessToken($accessToken, $type) {

        //查询数据库中有没有存在这个token

        return static::findIdentityByAccessToken($accessToken, $type);

    }

    public static function findByUsername($username)

    {

        return static::findOne(['username' => $username, 'status' => self::STATUS_ACTIVE]);

    }

    public function getId()

    {

        return $this->getPrimaryKey();

    }

    public function getAuthKey()

    {

        return $this->auth_key;

    }

    public function validateAuthKey($authKey)

    {

        return $this->getAuthKey() === $authKey;

}

这样一来,整个认证的过程就完成了。

现在对数据库user表中的access_token和访问时需携带的参数access-token进行简单说明:

1.user表中的参数access_token作用:

     在实现IdentityInterface接口类,重写findIdentityByAccessToken()方法中,根据access_token校验用户身份,获取用户信息

2.url访问时,通过携带的参数access-token作用:

     利用该参数进行认证。此参数其实是对yiifiltersauthQueryParanAuth中的属性$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会自动使用yiifiltersRateLimiter为yii estController配置一个行为过滤器来执行速率限制检查。

 如果速度超出限制,该速率限制器将抛出一个yiiwebTooManyRequestsHttpException。

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函数下添加以下代码:

 $behaviors['rateLimiter'] = [

        'class' => RateLimiter::className(),

        'enableRateLimitHeaders' => true,

];

3.在user表中使用两列来记录容差和时间戳信息。为了提高性能,可以考虑使用缓存或NoSQL存储这些信息。修改api/models/User.php,改动或者加入以下代码:

use yiifiltersRateLimitInterface;

class User extends ActiveRecord implements  IdentityInterface,RateLimitInterface

 

 //限制同一接口某时间段过多的请求,需要实现yiifiltersRateLimitInterface接口的全部方法====================

    // 返回某一时间允许请求的最大数量,比如设置60秒内最多5次请求(小数量方便我们模拟测试)

    public  function getRateLimit($request, $action){

        return [5, 60];

    }

 

    // 返回剩余的允许的请求和相应的UNIX时间戳数 当最后一次速率限制检查时

    public  function loadAllowance($request, $action){

        return [$this->allowance, $this->allowance_updated_at];

    }

 

    // 保存允许剩余的请求数和当前的UNIX时间戳

    public  function saveAllowance($request, $action, $allowance, $timestamp){

        $this->allowance = $allowance;

        $this->allowance_updated_at = $timestamp;

        $this->save();

   }

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

 

 

 

 

原文地址:https://www.cnblogs.com/hld123/p/6283619.html