做完了基本的 restful 搭建,就需要接口认证和定义返回码了
一、yii2 支持的 3种认证方式
1、HTTP 基本认证: \yii\filters\auth\HttpBasicAuth
支持两种认证方式,输入用户名和密码和只输入用户名(或 access_token)
(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() 方法
(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加密字符串
2、OAuth2认证: \yii\filters\auth\HttpBearerAuth
从认证服务器上获取基于OAuth2协议的access token,然后通过 HTTP Bearer Tokens 发送到API 服务器。
同样也是客户端 header中传入 Authorization:Bearer xxxxxx,然后在你的 user identity class 类中实现 findIdentityByAccessToken() 方法
3、JSONP请求: \yii\filters\auth\QueryParamAuth
在 URL请求参数中加入 access_token,这种方式应主要用于JSONP请求,因为它不能使用 HTTP 头来发送access token
比如:http://localhost/user/index/index?access-token=123
二、根据需求,为 restful api 增加业务逻辑增加验证和接口返回码
1、业务需求
(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
2、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;
3、为了以后方便修改和扩展,写一个 rest controller 基类,\frontend\extensions\RestApiBaseController,不用自带的 \yii\rest\ActiveController,大体上和 \yii\rest\ActiveController 差不多
<?phpnamespace 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 = []) { } }
4、实现 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]); }
5、GoodsController 继承的父类,改成 RestApiBaseController
6、错误码和出现错误时抛出的异常统一管理,编写 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 类
<?phpnamespace 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); } }
7、编写 sign 验证类 HttpSignAuth
<?phpnamespace 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; } }
8、增加包含用户登录和注册接口的 UserController
<?phpnamespace 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, ] ]; } }
9、frontend/config/main.php 中,优化用户注册、登录接口的 url
'POST v1/login' => '/v1/user/login','POST v1/register' => 'v1/user/register',
10、测试
(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
热门评论
这个东西做了好几天了,还是做不出来