Yii2.0 RESTful API 認證教程【令牌驗證】

最近在做RESTful API認證功能,記錄整個過程,方便以後查看。本文參照了 https://segmentfault.com/a/1190000016368603部分內容,感謝該作者的分享,以上內容根據我的項目實際情況進行了調整。

認證介紹

和Web應用不同,RESTful APIs 通常是無狀態的, 也就意味着不應使用 sessionscookies, 因此每個請求應附帶某種授權憑證,因爲用戶授權狀態可能沒通過 sessionscookies 維護, 常用的做法是每個請求都發送一個祕密的 access token 來認證用戶, 由於 access token 可以唯一識別和認證用戶,API 請求應通過 HTTPS 來防止man-in-the-middle (MitM) 中間人攻擊.

認證方式

  • HTTP 基本認證 :access token 當作用戶名發送,應用在access token可安全存在API使用端的場景, 例如,API使用端是運行在一臺服務器上的程序。
  • 請求參數: access token 當作API URL請求參數發送,例如 https://example.com/users?acc..., 由於大多數服務器都會保存請求參數到日誌, 這種方式應主要用於JSONP 請求,因爲它不能使用HTTP頭來發送 access token
  • OAuth 2 : 使用者從認證服務器上獲取基於 OAuth2 協議的 access token, 然後通過 HTTP Bearer Tokens 發送到 API 服務器。

上方進行簡單介紹,內容來自 Yii Framework 2.0 權威指南

實現步驟

繼續上一篇 的內容(這裏暫時使用默認User數據表,正式環境請分離不同的數據表來進行認證)

需要添加的數據內容

上篇User 數據表,我們還需要增加一 個 access_tokenexpire_at 的字段,

  • 進入項目根目錄打開控制檯輸入以下命令:
./yii migrate/create add_column_access_token_to_user
./yii migrate/create add_column_expire_at_to_user
  • 打開 你的項目目錄 /console/migrations/m181224_075747_add_column_access_token_user.php 修改如下內容:
    public function up()
    {
        $ret = $this->db->createCommand("SELECT * FROM information_schema.columns WHERE table_schema = DATABASE()  AND table_name = 'user' AND column_name = 'access_token'")->queryOne();//判斷user表是否有'access_token'這個字段
        if (empty($ret)) {
            $this->addColumn('user', 'access_token', $this->string(255)->defaultValue(NULL)->comment('令牌'));
        }
    }

    public function down()
    {
        $this->dropColumn('user', 'access_token');
        return true;
    }
  • 打開 你的項目目錄 /console/migrations/m181224_092333_add_column_expire_at_user.php 修改如下內容:
    public function up()
    {
        $ret = $this->db->createCommand("SELECT * FROM information_schema.columns WHERE table_schema = DATABASE()  AND table_name = 'user' AND column_name = 'expire_at'")->queryOne();
        if (empty($ret)) {
            $this->addColumn('user', 'expire_at', $this->integer(11)->defaultValue(NULL)->comment('令牌過期時間'));
        }
    }

    public function down()
    {
        $this->dropColumn('user', 'expire_at');
        return true;
    }
  • 執行遷移命令
./yii migrate

配置

打開 api\config\main.php

  • 配置 user 應用組件:
'user' => [
            'identityClass' => 'api\models\User',
            'enableAutoLogin' => true,
            'enableSession'=>false,
            //'identityCookie' => ['name' => '_identity-api', 'httpOnly' => true],
        ],
  • 將 session 組件註釋掉,或刪掉
//        'session' => [
//            // this is the name of the session cookie used for login on the backend
//            'name' => 'advanced-api',
//        ],
  • 編寫 api\models\User.php 實現認證類,繼承 IdentityInterface

common\models\User 類拷貝到 api\models\ 目錄下,修改命名空間爲 api\models

<?php
namespace api\models;

use Yii;
use yii\base\NotSupportedException;
use yii\behaviors\TimestampBehavior;
use yii\db\ActiveRecord;
use yii\web\IdentityInterface;
...
class User extends ActiveRecord implements IdentityInterface
{
    ...
    ...
}
  • commonmodelsLoginForm.php 類拷貝到 apimodels* 目錄下,修改命名空間,並重寫 login* 方法:
<?php
namespace api\models;

use Yii;
use yii\base\Model;
...
...

const EXPIRE_TIME = 604800;//令牌過期時間,7天有效

public function login()
{
        if ($this->validate()) {
            //return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600 * 24 * 30 : 0);
            if ($this->getUser()) {
                $access_token = $this->_user->generateAccessToken();
                $this->_user->expire_at = time() + static::EXPIRE_TIME;
                $this->_user->save();
                Yii::$app->user->login($this->_user, static::EXPIRE_TIME);
                return $access_token;
            }
        }
        return false;
}
  • 上方代碼給 User 模型添加了一個 generateAccessToken() 方法,因此我們到 api\models\User.php 中添加此方法
namespace api\models;

use Yii;
use yii\base\NotSupportedException;
use yii\behaviors\TimestampBehavior;
use yii\db\ActiveRecord;
use yii\web\IdentityInterface;
use yii\web\UnauthorizedHttpException;
...
...
class User extends ActiveRecord implements IdentityInterface
{
    ...
    ...
    
   /**
     * 生成accessToken字符串
     * @return string
     * @throws \yii\base\Exception
     */
    public function generateAccessToken()
    {
        $this->access_token=Yii::$app->security->generateRandomString();
        return $this->access_token;
    }
}
  • 接下來在api\controllers\新加一個控制器 命名爲 UserController 並繼承 yii\rest\ActiveController,編寫登錄 Login 方法,具體代碼如下:
namespace api\controllers;
use api\models\LoginForm;
use yii\rest\ActiveController;
use yii;


class UserController extends ActiveController
{
    public $modelClass = 'api\models\User';

    public function actions()
    {
        $action= parent::actions(); // TODO: Change the autogenerated stub
        unset($action['index']);
        unset($action['create']);
        unset($action['update']);
        unset($action['delete']);
    }

    public function actionIndex()
    {
        //你的代碼
    }

    /**
     * 登陸
     * @return array
     * @throws \yii\base\Exception
     * @throws \yii\base\InvalidConfigException
     */
    public function actionLogin()
    {
        $model = new LoginForm();
        if ($model->load(Yii::$app->getRequest()->getBodyParams(), '') && $model->login()) {
            return [
                'access_token' => $model->login(),
            ];
        } else {
            return $model->getFirstErrors();
        }
    }
}
  • 最後新增一條 URL 規則

打開 api\config\main.php 修改 components 屬性,添加下列代碼:

'urlManager' => [
    'enablePrettyUrl' => true,
    'enableStrictParsing' => true,
    'showScriptName' => false,
    'rules' => [
        ['class' => 'yii\rest\UrlRule', 
            'controller' => 'user',
            'extraPatterns'=>[
                'POST login'=>'login',
            ],
        ],
    ],
]

使用一個調試工具來進行測試 http://youdomain/users/login 記住是POST 請求發送,假如用POSTMAN有問題的話指定一下 Content-Type:application/x-www-form-urlencoded
ok,不出意外的話,相信你已經可以收到一個access_token 了,接下來就是如何使用這個token,如何維持認證狀態,達到不攜帶這個token將無法訪問,返回 401

維持認證狀態

實現認證步驟:

  1. 在你的 REST 控制器類中配置 authenticator 行爲來指定使用哪種認證方式
  2. 在你的 user identity class 類中實現 yiiwebIdentityInterface::findIdentityByAccessToken() 方法.

具體實現方式如下:

  • 打開之前的 User 控制器( api\controllers\UserController.php ),增加以下內容:
use yii\helpers\ArrayHelper;
use yii\filters\auth\QueryParamAuth;

...//此處省略一些代碼了

    public function behaviors()
    {
        return ArrayHelper::merge(parent::behaviors(), [
            'authenticatior' => [
                'class' => QueryParamAuth::className(), //實現access token認證
                'except' => ['login'], //無需驗證access token的方法,注意區分$noAclLogin
            ]
        ]);
    }
...    
  • 實現 findIdentityByAccessToken() 方法:

打開 api\models\User.php 重寫 findIdentityByAccessToken() 方法

...
use yii\web\UnauthorizedHttpException;
...

class User extends ActiveRecord implements IdentityInterface
{
    ...
    ...
    
    /**
     * {@inheritdoc}
     */
    public static function findIdentityByAccessToken($token, $type = null)
    {
//        throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.');
        $user = static::find()->where(['access_token' => $token, 'status' => self::STATUS_ACTIVE])->one();
        if (!$user) {
            return false;
        }
        if ($user->expire_at < time()) {
            throw new UnauthorizedHttpException('the access - token expired ', -1);
        } else {
            return $user;
        }
    }
    ...
}
  • 打開 api\controllers\UserController.php ,增加 Test方法,用於測試令牌驗證
    public function actionTest()
    {
        return ['status'=>'success'];
    }
  • 修改 api\config\main.php
        'urlManager' => [
            'enablePrettyUrl' => true,
            'enableStrictParsing' => true,
            'showScriptName' => false,
            'rules' => [
                ['class' => 'yii\rest\UrlRule',
                    'controller' => 'user',
                    //'pluralize' => false,    //設置爲false 就可以去掉複數形式了
                    'extraPatterns'=>[
                        'GET test'=>'test',
                        'POST login'=>'login',
                    ],
                ],
            ],
        ]

接下來訪問一下你的域名 http://youdomain/users/test,不攜帶任何參數是不是返回 401了?
ok,這裏介紹兩種訪問方式,一種是URL訪問,另一種是通過header 來進行攜帶

  1. http://youdomain/users/test?a...
  2. 傳遞 header 頭信息
Authorization:Bearer YYdpiZna0hJGhjsfqwxUeHEgLDfHEjB-

注意 Bearer 和你的token中間是有 一個空格的,很多同學在這個上面碰了很多次
以上就是基於YII2.0 RESTful 認證的內容。

本文參照了 https://segmentfault.com/a/1190000016368603部分內容,感謝該作者的分享,以上內容根據我的項目實際情況進行了調整。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章