Yii2 restful api建立,認證受權以及速率控制

Yii2 restful api建立,認證受權以及速率控制

下面是對restful從建立到速率控制的一個詳細流程介紹,裏面的步驟以及截圖儘量詳細,熟悉restful的盆友可能以爲過於繁瑣,新手不妨耐心仔細看一下。php

一.Api的建立html

1.複製一個frontend或者backend,將其重命名爲api放在同級目錄下git

 

2.而後刪除controllersviews文件夾,而後將api文件中的frontend替換爲api(好比命名空間,相關配置等),這點很是重要!!!web

 

 

3.打開cmd命令行,cd進入項目所在根目錄C:\wamp\www\advanced下,init 根據提示進行初始化設置,以後修改common\config\main-local.php文件中的數據庫相關配置。這個比較簡單。sql

4.須要修改common\config\bootstrap.php文件,對新建的應用增長alias別名,以下:  數據庫

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

5.爲新建的api應用程序美化路由,爲了後續方便訪問,進行域名配置json

  1)首先保證你的web服務器開啓rewrite規則,細節咱們就不說了,不過這是前提。bootstrap

  2)給api目錄配置一個訪問地址:www.dsgn.com指向 api/web,不會的盆友請參考個人另外一篇博客,關於Apache的域名配置api

  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表,字段本身隨意設置,並向其中插入幾條數據。

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' => 'api\modules\v1\Module',

        ],

    ],

  2)修改controllerNamespace

    'controllerNamespace' => 'api\controllers',

  3)開啓urlManager,以下配置

'urlManager' => [

    'enablePrettyUrl' => true,

    'enableStrictParsing' => true,

    'showScriptName' => false,

 //Goods配置Url規則

    'rules' => [

        ['class' => 'yii\rest\UrlRule', 'controller' => ['v1/good']],

    ],

],

 4刪除

   'errorHandler' => [

        'errorAction' => 'site/error',

   ],

 9. 從新配置控制器。爲了實現restful風格的api,yii2中,咱們須要對 api\modules\v1\controllers下的GoodController控制器進行一下改寫

 <?php

namespace api\modules\v1\controllers;

 

use yii\rest\ActiveController;

 

class GoodController extends ActiveController

{

    public $modelClass = 'api\models\Good';

}

 

說明:翻閱資料的時候,看到有人提出,在這個控制器裏面自定義方法,可是訪問的時候報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\rest包下(XXXAction.php就表示相應的動詞操做,源代碼不多,你能夠直接看看),

2)若是這個資源的全部動詞都是默認的話是則它的controller裏除了modelClass是不用寫任何代碼的.

3)若是想要自定義動詞必須繼承rest包下對應的Action,而後在controller重寫actions指向你的新action,從而實現重寫

打開文件api\modules\v1\controllers\UserController,參考代碼以下:

 

若是咱們想在本身定義不一樣的api接口的話?那麼咱們能夠經過配置實現,在main.php的主文件中:
'urlManager' => [
'enablePrettyUrl' => true,
'showScriptName' => false,
'rules' => [
[
'class' => 'yii\rest\UrlRule',
'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, 已經可以很方便的獲取咱們表中的數據了。

補充說明:1Yii 將在末端使用的控制器的名稱自動變爲複數。這個要重點注意!

2)這個地方使用之因此使用goods這個複數,是由於在RESTful架構中,每一個網址表明一種資源(resource),因此網址中不能有動詞,只能有名詞,並且所用的名詞每每與數據庫的表格名對應。

通常來講,數據庫中的表都是同種記錄的"集合"collection,因此API中的名詞也應該使用複數。

3)固然,用yii2restfulapi,暫時能夠不用框架推薦的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

yiirestful要得到關聯表數據必需要帶一個expand參數代表須要使用的關聯數據

4)若是是用restful自帶的路由規則,要解決訪問存在的複數問題的話,須要添加'pluralize' => false這個條件,具體以下:

 'rules' => [

                  ['class' => 'yii\rest\UrlRule',

                   '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支持JSONXML格式,若是想指定返回數據的格式,須要配置yii\filters\ContentNegotiator::formats屬性

如今從新去訪問http://www.dsgn.com/v1/good就能夠看到以json格式解析出來了。O(_)O~~~

補充說明:

API的目錄結構有2種方式

以前的建立測試都是在方式1的狀況下進行的,其實這兩種方式的差別並不大,如今對方式2進行補充說明:

打開api\config\main.php中,有以下配置:

2、認證受權

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) 設置yii\web\User::enableSession屬性爲false(由於RESTful APIs爲無狀態的,當yii\web\User::enableSessionfalse,請求中的用戶認證狀態就不能經過session來保持)

    2) 設置yii\web\User::loginUrl屬性爲null(顯示一個HTTP 403 錯誤而不是跳轉到登陸界面)

          3) 'identityClass' => 'api\models\User',

  指定認證的model類,如今是在api\models\User這個類中,那麼咱們須要在api\models\User這個類中實現,yii\web\IdentityInterface這個類中的全部定義的接口方法

    'user' => [

            'identityClass' => 'api\models\User',

            'enableAutoLogin' => true,

            'enableSession'=>false,

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

        ],

4.爲控制器配置authenticator行爲指定認證方式

<?php

namespace api\modules\v1\controllers;

use yii\rest\ActiveController;

use yii\helpers\ArrayHelper;

use yii\filters\auth\QueryParamAuth;

class UserController extends ActiveController

{

    public $modelClass = 'api\models\User';

 

    public function behaviors()  {

        $behaviors = parent::behaviors();

        $behaviors['authenticator'] = [

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

        ];

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

              'application/json'=> Response::FORMAT_JSON

        ];

        return $behaviors;

    }

}

說明: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中代碼以下:

<?php

namespace api\models;

use yii\db\ActiveRecord;

use yii\web\IdentityInterface;

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

    }

    //這個就是咱們進行yii\filters\auth\QueryParamAuth調用認證的函數,下面會說到。

    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做用:

  利用該參數進行認證。此參數實際上是對yii\filters\auth\QueryParanAuth中的屬性$tokenParam設置的值。

 細心的盆友會發現,這實際上是UserController中行爲中配置authenticator行爲對應的認證類。

 

  $tokenParam這個屬性是設置url附帶的token的參數key,咱們能夠在behaviors()這個函數中配置修改:

更改以後url附帶的參數key就應該是token.

4.如此一來,咱們先經過postman模擬不帶access-token請求看結果

 

 

 

提示401 咱們沒有權限訪問!

 

5.咱們在請求的連接上攜帶正確的access-token,認證經過後,控制器會再繼續執行其餘檢查(頻率限制、操做權限等),才能夠返回正確的用戶信息。

若是攜帶access-tokenuser表中找不到與該字段對應的值,那麼結果跟步驟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 = [])

    {

    }

3、速率控制

說明:爲防止濫用,能夠增長速率限制。例如,限制每一個用戶的API的使用是在60秒內最多5次的API調用,

若是一個用戶同一個時間段內太多的請求被接收,將返回響應狀態代碼 429 (這意味着過多的請求)

1.在咱們啓用了速率限制後,Yii會自動使用yii\filters\RateLimiteryii\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函數下添加如下代碼:

 $behaviors['rateLimiter'] = [

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

        'enableRateLimitHeaders' => true,

];

3.user表中使用兩列來記錄容差和時間戳信息。爲了提升性能,能夠考慮使用緩存或NoSQL存儲這些信息。修改api/models/User.php,改動或者加入如下代碼:

use yii\filters\RateLimitInterface;

class User extends ActiveRecord implements  IdentityInterface,RateLimitInterface

 

 //限制同一接口某時間段過多的請求,須要實現yii\filters\RateLimitInterface接口的所有方法====================

    // 返回某一時間容許請求的最大數量,好比設置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.錯誤處理

YiiREST框架的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: 內部服務器錯誤。 這多是因爲內部程序錯誤引發的。

相關文章
相關標籤/搜索