php restful 认证,yii2 restful api 风格搭建(二)接口认证

最近在研究如何利用 yii2 搭建 restful api,将 yii2 restful api / yii2 rest api 搭建心得写下,欢迎一起讨论

做完了yii2 restful api 搭建,就需要进行 auth 接口认证和定义返回码了

一、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 差不多

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 = [])

{

}

}

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 类

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 类

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);

}

}

7、编写 sign 验证类 HttpSignAuth

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;

}

}

8、增加包含用户登录和注册接口的 UserController

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,

]

];

}

}

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"}}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值