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(获取用户信息,执行整个接口校验流程)
登录鉴权图

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

代码展示
common.php
<?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
<?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
<?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
<?php
return [
//redis的配置
'redis' => [
'host' => 'localhost',
'port' => '6379',
'auth' => '123456',
'db_id' => 0,//redis的第几个数据库仓库
],
//是否开启接口校验,true开启,false,关闭
'checkApi'=>true,
//加密sign的盐值
'apiSerect'=>'test_jwt'
];
login.php
<?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
<?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的使用。
php接口数据安全解决方案(一)的更多相关文章
- php接口数据安全解决方案(二)
前言 实例演示token签名并创建token 解析token并校验token合法性 类库封装管理jwt实例 前言 JWT是什么 JWT是json web token缩写.它将用户信息加密到token里 ...
- Oracle数据安全解决方案(1)——透明数据加密TDE
Oracle数据安全解决方案(1)——透明数据加密TDE2009年09月23日 22:49:00 华仔爱技术 阅读数:7991原文地址: http://www.oracle.com/technolog ...
- Sentry 企业级数据安全解决方案 - Relay 运行模式
内容整理自官方开发文档 Relay 可以在几种主要模式之一下运行,如果您正在配置 Relay server 而不是使用默认设置,那么事先了解这些模式至关重要. 模式存储在配置文件中,该文件包含 rel ...
- Sentry 企业级数据安全解决方案 - Relay 配置选项
Relay 的配置记录在文件 .relay/config.yml 中.要更改此位置,请将 --config 选项传递给任何 Relay 命令: ❯ ./relay run --config /path ...
- Sentry 企业级数据安全解决方案 - Relay 监控 & 指标收集
内容整理自官方文档 系列 Sentry 企业级数据安全解决方案 - Relay 入门 Sentry 企业级数据安全解决方案 - Relay 运行模式 Sentry 企业级数据安全解决方案 - Rela ...
- Sentry 企业级数据安全解决方案 - Relay 项目配置
内容整理自官方文档 系列 Sentry 企业级数据安全解决方案 - Relay 入门 Sentry 企业级数据安全解决方案 - Relay 运行模式 Sentry 企业级数据安全解决方案 - Rela ...
- Sentry 企业级数据安全解决方案 - Relay 操作指南
内容整理自官方文档 本篇回顾了我们在自托管外部使用 Relay 时的操作指南,即在您的硬件上运行的 Relay 并将事件转发到 sentry.io. 系列 Sentry 企业级数据安全解决方案 - R ...
- Sentry 企业级数据安全解决方案 - Relay PII 和数据清理
本文档描述了一种我们希望最终对用户隐藏的配置格式.该页面仍然存在的唯一原因是当前 Relay 接受这种格式以替代常规数据清理设置. 以下文档探讨了 Relay 使用和执行的高级数据清理配置的语法和语义 ...
- Windows7下出现“不支持此接口”的解决方案
今天学校里的辅导员突然找到我说Windows 7下什么文件夹都打不开了,提示“不支持此接口”.怀疑是病毒所致,但运行杀毒软件没有结果.重启也问题依旧. 上网查了之后找到了修复方法: 在命令行中输入fo ...
随机推荐
- Nginx- web服务配置与测试
(一) 软件介绍由俄罗斯人lgor Sysove开发,为开源软件.支持高并发:支持几万并发连接(特别是静态小文件业务环境) 资源消耗少:在3万并发连接下开启10个Nginx线程消耗内存不到200M 支 ...
- centos 安装ELK
准备安装环境 由于本人的centos是通过虚拟机来进行安装的,为了本地电脑能够访问centos系统中的端口,则需要把防火墙进行关闭,通过以下方式进行关闭防火墙. # vi /etc/sysconfig ...
- ubuntu18 搭建ftp服务器,以及文件目录权限问题
有时候呢我们有一台本地的台式机或者云服务器,我们想要搭个ftp服务器好让我们在内网/外网中方便的传输.保存文件,这样别的任何电脑啊,设备啊,只要访问这个ftp的地址,就可以进行文件传输啦!由于我现在台 ...
- CommandLineParse类(命令行解析类)
https://blog.csdn.net/jkhere/article/details/8674019 https://sophia0130.github.io/2018/05/08/Command ...
- 链表(python)
链表1.为什么需要链表顺序表的构建需要预先知道数据大小来申请连续的存储空间,而在进行扩充时又需要进行数据的搬迁,所以使用起来并不是很灵活.链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理. ...
- Nginx入门(二)——双机热备
upstream backend { server ; server backup; } server { listen ; server_name localhost; #charset koi8- ...
- centOS7 下 安装mysql8.x
第一部分 CentOS7安装mysql1.1 安装前清理工作:1.1.1 清理原有的mysql数据库:使用以下命令查找出安装的mysql软件包和依赖包: rpm -pa | grep mysql 显示 ...
- 使用Java实现数据库编程-----------查询学生记录
查询所有学生记录,包含年级名称 @Override public LIst<Student>getAllStudent() throws Exception{ List<Studen ...
- 【素数判定/筛法进阶算法】-C++
今天我们来谈一谈素数的判定/筛法. 对于每一个OIer来说,在漫长的练习过程中,素数不可能不在我们的眼中出现,那么判定/筛素数也是每一个OIer应该掌握的操作,那么我们今天来分享几种从暴力到高效的判定 ...
- jQuery相关方法4-----元素创建和移除
一.创建添加元素 父元素.append(子元素)-----被动追加创建 子元素.appendTo(父元素)-----主动追加创建 <script src="http://libs.ba ...