php 接口安全解决方案,php接口数据安全解决方案(一)

前言

目的:

1.实现前后端代码分离,分布式部署

2.利用token替代session实现状态保持,token是有时效性的满足退出登录,token存入redis可以解决不同服务器之间session不同步的问题,满足分布式部署

3.利用sign,前端按照约定的方式组合加密生成字符串来校验用户传递的参数跟后端接收的参数是否一直,保障接口数据传递的安全

4.利用nonce,timestamp来保障每次请求的生成sign不一致,并将sign与nonce组合存入redis,来防止api接口重放

目录介绍

├── Core

│   ├── Common.php(常用的公用方法)

│   ├── Controller.php (控制器基类)

│   └── RedisService.php (redis操作类)

├── config.php (redis以及是否开启关闭接口校验的配置项)

├── login.php (登录获取token入口)

└── user.php(获取用户信息,执行整个接口校验流程)

登录鉴权图

d6cf9278833613a3b94ceb2af950da8b.png

接口请求安全性校验整体流程图

2da5c39af1341372bf3cef31b87f383e.png

代码展示

common.php

namespace Core;

/**

* @desc 公用方法

* Class Common

*/

class Common{

/**

* @desc 输出json数据

* @param $data

*/

public static function outJson($code,$msg,$data=null){

$outData = [

'code'=>$code,

'msg'=>$msg,

];

if(!empty($data)){

$outData['data'] = $data;

}

echo json_encode($outData);

die();

}

/***

* @desc 创建token

* @param $uid

*/

public static function createToken($uid){

$time = time();

$rand = mt_rand(100,999);

$token = md5($time.$rand.'jwt-token'.$uid);

return $token;

}

/**

* @desc 获取配置信息

* @param $type 配置信息的类型,为空获取所有配置信息

*/

public static function getConfig($type=''){

$config = include "./config.php";

if(empty($type)){

return $config;

}else{

if(isset($config[$type])){

return $config[$type];

}

return [];

}

}

}

RedisService.php

namespace Core;

/*

*@desc redis类操作文件

**/

class RedisService{

private $redis;

protected $host;

protected $port;

protected $auth;

protected $dbId=0;

static private $_instance;

public $error;

/*

*@desc 私有化构造函数防止直接实例化

**/

private function __construct($config){

$this->redis = new \Redis();

$this->port = $config['port'] ? $config['port'] : 6379;

$this->host = $config['host'];

if(isset($config['db_id'])){

$this->dbId = $config['db_id'];

$this->redis->connect($this->host, $this->port);

}

if(isset($config['auth']))

{

$this->redis->auth($config['auth']);

$this->auth = $config['auth'];

}

$this->redis->select($this->dbId);

}

/**

*@desc 得到实例化的对象

***/

public static function getInstance($config){

if(!self::$_instance instanceof self) {

self::$_instance = new self($config);

}

return self::$_instance;

}

/**

*@desc 防止克隆

**/

private function __clone(){}

/*

*@desc 设置字符串类型的值,以及失效时间

**/

public function set($key,$value=0,$timeout=0){

if(empty($value)){

$this->error = "设置键值不能够为空哦~";

return $this->error;

}

$res = $this->redis->set($key,$value);

if($timeout){

$this->redis->expire($key,$timeout);

}

return $res;

}

/**

*@desc 获取字符串类型的值

**/

public function get($key){

return $this->redis->get($key);

}

}

Controller.php

namespace Core;

use Core\Common;

use Core\RedisService;

/***

* @desc 控制器基类

* Class Controller

* @package Core

*/

class Controller{

//接口中的token

public $token;

public $mid;

public $redis;

public $_config;

public $sign;

public $nonce;

/**

* @desc 初始化处理

* 1.获取配置文件

* 2.获取redis对象

* 3.token校验

* 4.校验api的合法性check_api为true校验,为false不用校验

* 5.sign签名验证

* 6.校验nonce,预防接口重放

*/

public function __construct()

{

//1.获取配置文件

$this->_config = Common::getConfig();

//2.获取redis对象

$redisConfig = $this->_config['redis'];

$this->redis = RedisService::getInstance($redisConfig);

//3.token校验

$this->checkToken();

//4.校验api的合法性check_api为true校验,为false不用校验

if($this->_config['checkApi']){

// 5. sign签名验证

$this->checkSign();

//6.校验nonce,预防接口重放

$this->checkNonce();

}

}

/**

* @desc 校验token的有效性

*/

private function checkToken(){

if(!isset($_POST['token'])){

Common::outJson('10000','token不能够为空');

}

$this->token = $_POST['token'];

$key = "token:".$this->token;

$mid = $this->redis->get($key);

if(!$mid){

Common::outJson('10001','token已过期或不合法,请先登录系统 ');

}

$this->mid = $mid;

}

/**

* @desc 校验签名

*/

private function checkSign(){

if(!isset($_GET['sign'])){

Common::outJson('10002','sign校验码为空');

}

$this->sign = $_GET['sign'];

$postParams = $_POST;

$params = [];

foreach($postParams as $k=>$v) {

$params[] = sprintf("%s%s", $k,$v);

}

sort($params);

$apiSerect = $this->_config['apiSerect'];

$str = sprintf("%s%s%s", $apiSerect, implode('', $params), $apiSerect);

if ( md5($str) != $this->sign ) {

Common::outJson('10004','传递的数据被篡改,请求不合法');

}

}

/**

* @desc nonce校验预防接口重放

*/

private function checkNonce(){

if(!isset($_POST['nonce'])){

Common::outJson('10003','nonce为空');

}

$this->nonce = $_POST['nonce'];

$nonceKey = sprintf("sign:%s:nonce:%s", $this->sign, $this->nonce);

$nonV = $this->redis->get($nonceKey);

if ( !empty($nonV)) {

Common::outJson('10005','该url已经被调用过,不能够重复使用');

} else {

$this->redis->set($nonceKey,$this->nonce,360);

}

}

}

config.php

return [

//redis的配置

'redis' => [

'host' => 'localhost',

'port' => '6379',

'auth' => '123456',

'db_id' => 0,//redis的第几个数据库仓库

],

//是否开启接口校验,true开启,false,关闭

'checkApi'=>true,

//加密sign的盐值

'apiSerect'=>'test_jwt'

];

login.php

/**

* @desc 自动加载类库

*/

spl_autoload_register(function($className){

$arr = explode('\\',$className);

include $arr[0].'/'.$arr[1].'.php';

});

use Core\Common;

use Core\RedisService;

if(!isset($_POST['username']) || !isset($_POST['pwd']) ){

Common::outJson(-1,'请输入用户名和密码');

}

$username = $_POST['username'];

$pwd = $_POST['pwd'];

if($username!='admin' || $pwd!='123456' ){

Common::outJson(-1,'用户名或密码错误');

}

//创建token并存入redis,token对应的值为用户的id

$config = Common::getConfig('redis');

$redis = RedisService::getInstance($config);

//假设用户id为2

$uid = 2;

$token = Common::createToken($uid);

$key = "token:".$token;

$redis->set($key,$uid,3600);

$data['token'] = $token;

Common::outJson(0,'登录成功',$data);

user.php

/**

* @desc 自动加载类库

*/

spl_autoload_register(function($className){

$arr = explode('\\',$className);

include $arr[0].'/'.$arr[1].'.php';

});

use Core\Controller;

use Core\Common;

class UserController extends Controller{

/***

* @desc 获取用户信息

*/

public function getUser(){

$userInfo = [

"id"=>2,

"name"=>'巴八灵',

"age"=>30,

];

if($this->mid==$_POST['mid']){

Common::outJson(0,'成功获取用户信息',$userInfo);

}else{

Common::outJson(-1,'未找到该用户信息');

}

}

}

//获取用户信息

$user = new UserController();

$user->getUser();

演示用户登录

简要描述:

用户登录接口

请求URL:

http://localhost/login.php

请求方式:

POST

参数:

参数名

必选

类型

说明

username

string

用户名

pwd

string

密码

返回示例

{

"code": 0,

"msg": "登录成功",

"data": {

"token": "86b58ada26a20a323f390dd5a92aec2a"

}

}

{

"code": -1,

"msg": "用户名或密码错误"

}

演示获取用户信息

简要描述:

获取用户信息,校验整个接口安全的流程

请求URL:

http://localhost/user.php?sign=f39b0f2dea817dd9dbef9e6a2bf478de

请求方式:

POST

参数:

参数名

必选

类型

说明

token

string

token

mid

int

用户id

nonce

string

防止用户重放字符串 md5加密串

timestamp

int

当前时间戳

返回示例

{

"code": 0,

"msg": "成功获取用户信息",

"data": {

"id": 2,

"name": "巴八灵",

"age": 30

}

}

{

"code": "10005",

"msg": "该url已经被调用过,不能够重复使用"

}

{

"code": "10004",

"msg": "传递的数据被篡改,请求不合法"

}

{

"code": -1,

"msg": "未找到该用户信息"

}

文章完整代码地址

后记

上面完整的实现了整个api的安全过程,包括接口token生成时效性合法性验证,接口数据传输防篡改,接口防重放实现。仅仅靠这还不能够最大限制保证接口的安全。条件满足的情况下可以使用https协议从数据底层来提高安全性,另外本实现过程token是使用redis存储,下一篇文章我们将使用第三方开发的库实现JWT的规范操作,来替代redis的使用。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值