作完了基本的 restful 搭建,就須要接口認證和定義返回碼了php
一、HTTP 基本認證: \yii\filters\auth\HttpBasicAuthios
支持兩種認證方式,輸入用戶名和密碼和只輸入用戶名(或 access_token)web
(1)默認是隻輸入用戶名(或acdess_token)算法
The default implementation of HttpBasicAuth uses the [[\yii\web\User::loginByAccessToken()|loginByAccessToken()]] method of the `user` application component and only passes the user name. This implementation is used for authenticating API clients.
只輸入用戶名認證須要在你的 user identity class 類中實現 findIdentityByAccessToken() 方法api
(2)若是須要驗證用戶名和密碼,HttpBasicAuth 中的註釋中也說明了配置方法服務器
public function behaviors() { return [ 'basicAuth' => [ 'class' => \yii\filters\auth\HttpBasicAuth::className(), 'auth' => function ($username, $password) { $user = User::find()->where(['username' => $username])->one(); if ($user->verifyPassword($password)) { return $user; } return null; }, ], ]; }
客戶端調用時,能夠header中傳入 Authorization:Basic 用戶名:密碼 (或只用戶名/access_token)的base64加密字符串restful
二、OAuth2認證: \yii\filters\auth\HttpBearerAuthyii2
從認證服務器上獲取基於OAuth2協議的access token,而後經過 HTTP Bearer Tokens 發送到API 服務器。
一樣也是客戶端 header中傳入 Authorization:Bearer xxxxxx,而後在你的 user identity class 類中實現 findIdentityByAccessToken() 方法app
三、JSONP請求: \yii\filters\auth\QueryParamAuthfrontend
在 URL請求參數中加入 access_token,這種方式應主要用於JSONP請求,由於它不能使用 HTTP 頭來發送access token
好比:http://localhost/user/index/index?access-token=123
一、業務需求
(1)用戶註冊接口
(2)用戶登陸接口
(3)獲取商品信息接口
(4)三個接口在調用時,都要傳遞 sign 參數, 若是客戶端傳遞的 sign 參數和服務端計算出的 sign 不一致,就認爲是非法請求,sign 參數的加密算法是
isset($params['sign']) && unset($params['sign']); ksort($params); //$privateKey 爲客戶端和服務端協商好的一個祕鑰 $sign = md5($privateKey . implode(',', $params))
(5)用戶註冊接口和登陸接口,不須要 access_token 驗證,獲取商品信息接口 須要 access_token 驗證,access_token 的驗證就使用 yii2 自帶的 \yii\filters\auth\HttpBasicAuth
二、user 表就用 yii2 自帶的 user 表
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) COLLATE utf8_unicode_ci NOT NULL, `auth_key` varchar(32) COLLATE utf8_unicode_ci NOT NULL, `password_hash` varchar(255) COLLATE utf8_unicode_ci NOT NULL, `password_reset_token` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, `email` varchar(255) COLLATE utf8_unicode_ci NOT NULL, `status` smallint(6) NOT NULL DEFAULT '10', `created_at` int(11) NOT NULL, `updated_at` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`), UNIQUE KEY `email` (`email`), UNIQUE KEY `password_reset_token` (`password_reset_token`) ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
三、爲了之後方便修改和擴展,寫一個 rest controller 基類,\frontend\extensions\RestApiBaseController,不用自帶的 \yii\rest\ActiveController,大致上和 \yii\rest\ActiveController 差很少
<?php namespace frontend\extensions; use yii\base\Model; use yii\rest\Controller; use yii\base\InvalidConfigException; use yii\filters\auth\HttpBasicAuth; use frontend\extensions\HttpSignAuth; class RestApiBaseController extends Controller { public $modelClass; /** * @var string the scenario used for updating a model. * @see \yii\base\Model::scenarios() */ public $updateScenario = Model::SCENARIO_DEFAULT; /** * @var string the scenario used for creating a model. * @see \yii\base\Model::scenarios() */ public $createScenario = Model::SCENARIO_DEFAULT; public function init() { parent::init(); if ($this->modelClass === null) { throw new InvalidConfigException('The "modelClass" property must be set.'); } } /** * 重寫 behaviors */ public function behaviors() { return [ //增長新的接口驗證類,參數加密的sign 'tokenValidate' => [ //參數加密的sign全部接口都須要驗證 'class' => HttpSignAuth::className(), ], 'authValidate' => [ 'class' => HttpBasicAuth::className(), //access-token 部分接口須要驗證,須要排除好比 login register 這樣的接口 'optional' => ['register', 'login'], ], ]; } public function actions() { return [ 'index' => [ 'class' => 'yii\rest\IndexAction', 'modelClass' => $this->modelClass, 'checkAccess' => [$this, 'checkAccess'], ], 'view' => [ 'class' => 'yii\rest\ViewAction', 'modelClass' => $this->modelClass, 'checkAccess' => [$this, 'checkAccess'], ], 'create' => [ 'class' => 'yii\rest\CreateAction', 'modelClass' => $this->modelClass, 'checkAccess' => [$this, 'checkAccess'], 'scenario' => $this->createScenario, ], 'update' => [ 'class' => 'yii\rest\UpdateAction', 'modelClass' => $this->modelClass, 'checkAccess' => [$this, 'checkAccess'], 'scenario' => $this->updateScenario, ], 'delete' => [ 'class' => 'yii\rest\DeleteAction', 'modelClass' => $this->modelClass, 'checkAccess' => [$this, 'checkAccess'], ], 'options' => [ 'class' => 'yii\rest\OptionsAction', ], ]; } /** * {@inheritdoc} */ protected function verbs() { return [ 'index' => ['GET', 'HEAD'], 'view' => ['GET', 'HEAD'], 'create' => ['POST'], 'update' => ['PUT', 'PATCH'], 'delete' => ['DELETE'], ]; } public function checkAccess($action, $model = null, $params = []) { } }
四、實現 user identity class 類中的 findIdentityByAccessToken,個人 user identity class 是 \frontend\models\User
public static function findIdentityByAccessToken($token, $type = null) { if(empty($token)){ return null; } return static::findOne(['auth_key' => $token, 'status' => self::STATUS_ACTIVE]); }
五、GoodsController 繼承的父類,改爲 RestApiBaseController
六、錯誤碼和出現錯誤時拋出的異常統一管理,編寫 ErrorCode 類和 ApiHttpException 類
(1)ErrorCode 類
<?php namespace frontend\extensions; class ErrorCode{ private static $error = [ 'system_error' => [ 'status' => 500, 'code' => 500000, 'msg' => 'system error', ], 'auth_error' => [ 'status'=> 401, 'code' => 400000, 'msg' => 'auth error', ], 'params_error' => [ 'status'=> 401, 'code' => 400001, 'msg' => 'params error', ], ]; private function __construct(){ } public static function getError($key){ if(empty($key) || !isset(self::$error[$key])){ throw new \Exception("error code not exist", 400); } return self::$error[$key]; } }
(2)ApiHttpException 類
<?php namespace frontend\extensions; use Yii; use yii\web\HttpException; class ApiHttpException extends HttpException{ public function __construct($status, $message = null, $code = 0, \Exception $previous = null) { $this->statusCode = $status; parent::__construct($status, $message, $code, $previous); } }
七、編寫 sign 驗證類 HttpSignAuth
<?php namespace frontend\extensions; use Yii; use yii\base\Behavior; use yii\web\Controller; use frontend\extensions\ErrorCode; use frontend\extensions\ApiHttpException; /** * sign 驗證類 */ class HttpSignAuth extends Behavior{ public $privateKey = '12345678'; public $signParam = 'sign'; public function events() { return [Controller::EVENT_BEFORE_ACTION => 'beforeAction']; } public function beforeAction($event) { //獲取 sign $sign = Yii::$app->request->get($this->signParam, null); $getParams = Yii::$app->request->get(); $postParams = Yii::$app->request->post(); $params = array_merge($getParams, $postParams); if(empty($sign) || !$this->checkSign($sign, $params)){ $error = ErrorCode::getError('auth_error'); throw new ApiHttpException($error['status'], $error['msg'], $error['code']); } return true; } private function checkSign($sign, $params) { unset($params[$this->signParam]); ksort($params); return md5($this->privateKey . implode(',', $params)) === $sign; } }
八、增長包含用戶登陸和註冊接口的 UserController
<?php namespace frontend\modules\v1\controllers; use Yii; use frontend\models\User; use frontend\extensions\ErrorCode; use frontend\extensions\ApiHttpException; use frontend\extensions\RestApiBaseController; class UserController extends RestApiBaseController { public $modelClass = 'frontend\models\User'; public function actionRegister(){ //爲了方便,這裏只作了很是簡單的參數驗證 if(!Yii::$app->request->isPost){ $error = ErrorCode::getError('params_error'); throw new ApiHttpException($error['status'], $error['msg'], $error['code']); } $params = Yii::$app->request->post(); if(empty($params['name']) || empty($params['pwd']) || empty($params['email'])){ $error = ErrorCode::getError('params_error'); throw new ApiHttpException($error['status'], $error['msg'], $error['code']); } //用戶註冊 $user = new User(); $user->username = $params['name']; $user->email = $params['email']; $user->setPassword($params['pwd']); $user->generateAuthKey(); $user->save(false); return [ 'error_code' => 0, 'res_msg' => [ 'uid' => $user->primaryKey, 'token' => $user->authKey, ] ]; } public function actionLogin(){ //爲了方便,這裏只作了很是簡單的參數驗證 if(!Yii::$app->request->isPost){ $error = ErrorCode::getError('params_error'); throw new ApiHttpException($error['status'], $error['msg'], $error['code']); } $params = Yii::$app->request->post(); if(empty($params['name']) || empty($params['pwd'])){ $error = ErrorCode::getError('params_error'); throw new ApiHttpException($error['status'], $error['msg'], $error['code']); } $user = User::findByUsername($params['name']); if (!$user || !$user->validatePassword($params['pwd'])) { $error = ErrorCode::getError('auth_error'); throw new ApiHttpException($error['status'], $error['msg'], $error['code']); } return [ 'error_code' => 0, 'res_msg' => [ 'uid' => $user->primaryKey, 'token' => $user->authKey, ] ]; } }
九、frontend/config/main.php 中,優化用戶註冊、登陸接口的 url
'POST v1/login' => '/v1/user/login', 'POST v1/register' => 'v1/user/register',
十、測試
(1)錯誤的 sign 調用 register
命令: curl -X POST -s http://local.rest.com/v1/register?sign=sdasds 返回: {"code":401,"msg":"auth error"}
(2)正確的 sign,但是沒有傳 register 必須的參數 ($params = [])
命令: curl -X POST -s http://local.rest.com/v1/register?sign=25d55ad283aa400af464c76d713c07ad 返回: {"code":401,"msg":"params error"}
(3)正確的 sign,輸入 register 必須的參數
array( "name" => "smoke1", "email" => "smoke1@sina.com", "pwd" => "123456", )
命令: curl -X POST -d "name=smoke1&email=smoke1@sina.com&pwd=123456" -s http://local.rest.com/v1/register?sign=2e3ef98ccb57bf57f73ecd4745052c96 返回: {"code":0,"msg":{"uid":10,"token":"J1RS0lHs-XUzNWxj3LMtH15h1j81lPyo"}
(4)使用正確的 sign 錯誤 token 訪問 goods 接口
array( "id" => 1, )
命令: curl -X GET -H "Authorization:Basic dadsadsadsadsad" -s http://local.rest.com/v1/goods/1?sign=feb8dc0697a2e0a947c6e20dc4ec3ebc 返回: {"code":401,"msg":"Your request was made with invalid credentials."}
(5)使用正確的 sign,正確的 token 訪問 goods 接口
命令: curl -X GET -H "Authorization:Basic SjFSUzBsSHMtWFV6Tld4ajNMTXRIMTVoMWo4MWxQeW86" -s http://local.rest.com/v1/goods/1?sign=feb8dc0697a2e0a947c6e20dc4ec3ebc 返回: {"code":0,"msg":{"id":"1","name":"測試商品1","price":"600","status":1,"create_time":"1520490595","modify_time":"1520490595"}}
做者:smoke_zl
連接:https://www.jianshu.com/p/1d0e3628a14f
來源:簡書
著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。
Detect languageAfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBengaliBosnianBulgarianCatalanCebuanoChichewaChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDutchEnglishEsperantoEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekGujaratiHaitian CreoleHausaHebrewHindiHmongHungarianIcelandicIgboIndonesianIrishItalianJapaneseJavaneseKannadaKazakhKhmerKoreanLaoLatinLatvianLithuanianMacedonianMalagasyMalayMalayalamMalteseMaoriMarathiMongolianMyanmar (Burmese)NepaliNorwegianPersianPolishPortuguesePunjabiRomanianRussianSerbianSesothoSinhalaSlovakSlovenianSomaliSpanishSundaneseSwahiliSwedishTajikTamilTeluguThaiTurkishUkrainianUrduUzbekVietnameseWelshYiddishYorubaZulu |
|
AfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBengaliBosnianBulgarianCatalanCebuanoChichewaChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDutchEnglishEsperantoEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekGujaratiHaitian CreoleHausaHebrewHindiHmongHungarianIcelandicIgboIndonesianIrishItalianJapaneseJavaneseKannadaKazakhKhmerKoreanLaoLatinLatvianLithuanianMacedonianMalagasyMalayMalayalamMalteseMaoriMarathiMongolianMyanmar (Burmese)NepaliNorwegianPersianPolishPortuguesePunjabiRomanianRussianSerbianSesothoSinhalaSlovakSlovenianSomaliSpanishSundaneseSwahiliSwedishTajikTamilTeluguThaiTurkishUkrainianUrduUzbekVietnameseWelshYiddishYorubaZulu |
|
|
|
|
|