zoukankan      html  css  js  c++  java
  • 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: 内部服务器错误。 这可能是由于内部程序错误引起的。

     

     

     

     

  • 相关阅读:
    pat每日刷题计划--day70
    pat每日刷题计划--day69
    pat每日刷题计划--day68
    pat每日刷题计划--day67
    pat每日刷题计划--day66
    SpringBoot+MyBatis+MySQL读写分离
    SpringMVC源码分析
    ActiveMQ从入门到精通(三)
    ActiveMQ从入门到精通(二)
    ActiveMQ从入门到精通(一)
  • 原文地址:https://www.cnblogs.com/hld123/p/6283619.html
Copyright © 2011-2022 走看看