接口的安全性主要围绕Token、Timestamp和Sign三个机制展开设计,保证接口的数据不会被篡改和重复调用,下面具体来看:

(1)Token授权机制:(Token是客户端访问服务端的凭证)--用户使用用户名密码登录后服务器给客户端返回一个Token(通常是UUID),并将Token-UserId以键值对的形式存放在缓存服务器中。服务端接收到请求后进行Token验证,如果Token不存在,说明请求无效。

(2)时间戳超时机制:(签名机制保证了数据不会被篡改)用户每次请求都带上当前时间的时间戳timestamp,服务端接收到timestamp后跟当前时间进行比对,如果时间差大于一定时间(比如5分钟),则认为该请求失效。时间戳超时机制是防御DOS攻击的有效手段。

(3)签名机制:将 Token 和 时间戳 加上其他请求参数再用MD5或SHA-1算法(可根据情况加点盐)加密,加密后的数据就是本次请求的签名sign,服务端接收到请求后以同样的算法得到签名,并跟当前的签名进行比对,如果不一样,说明参数被更改过,直接返回错误标识。

/**
* @desc 接受参数处理
*/
private function dealParam(){
//接受header参数--系统参数
$systemParam=getAllHeadersParam();
//接受body数据--业务参数(json格式)
$data=file_get_contents('php://input'); //读取配置文件中的私钥信息
$api_apiKey=C('api_apiKey');
$privatekey=$api_apiKey[$systemParam['token']]; $arr['token'] =$systemParam['token']; //服务端分配的标识(不同客户端需使用不同的标识)
$arr['timestamp']=$systemParam['timestamp']; //时间戳,UTC时间,以北京时间东八区(+8)为准
$arr['version'] =$systemParam['version']; //版本号
$arr['sign'] =$systemParam['sign']; //签名
$arr['source'] =$systemParam['source']; //来源(0-安卓/1-IOS/2-H5/3-PC/4-php/5-java)
$arr['data'] =json_decode($data,true); //业务参数json格式
$arr['method'] =$data['method']; //访问接口,格式:模型名.方法名 return $arr;
}
/*
* @desc 获取所有以HTTP开头的header参数
* @return array
*/
private function getAllHeadersParam(){
$headers = array();
foreach($_SERVER as $key=>$value){
if(substr($key, 0, 5)==='HTTP_'){
$key = substr($key, 5);
$key = str_replace('_', ' ', $key);
$key = str_replace(' ', '-', $key);
$key = strtolower($key);
$headers[$key] = $value;
}
}
return $headers;
}
/*
* @desc 签名校验
* @param $token string 服务端分配的标识(不同客户端需使用不同的标识)
* @param $timestamp string 时间戳,UTC时间,以北京时间东八区(+8)为准
* @param $version string 版本号
* @param $sign string 签名
* @param $source int 来源(0-安卓/1-IOS/2-H5/3-PC/4-php/5-java)
* @param $privatekey string 私钥
* @param $data 业务参数json格式
* @return bool
*/
private function checkAuth($token,$timestamp,$version,$sign,$source,$privatekey,$data){
//参数判断
if(empty($token)){
E('token不能为空!');
}
if(empty($timestamp)){
E('时间戳不能为空!');
}
if(empty($version)){
E('版本号不能为空!');
}
if(empty($data)){
E('业务参数不能为空!');
}
if(empty($source) && $source<>''){
E('来源不能为空!');
}
if(empty($sign)){
E('签名不能为空!');
}
if(empty($privatekey)){
E('私钥不能为空!');
}
//时间校验
$expire_second=C('expire_second',null,);
$timestamp_t=$timestamp+$expire_second;
if($timestamp_t<time()){
E('请求已经过期!');
}
$public= D('public');
$datas=$this->original;
//系统参数
$paramArr=array(
'token'=>$token,
'timestamp'=>$timestamp,
'version'=>$version,
'source'=>$source,
'data'=>$data,
); //按规则拼接为字符串
$str = $this->createSign($paramArr,$this->privatekey); if($str != $this->sign){
E('验签错误!');
}
return true;
}

sign生成规则及步骤:

① 第一步:将所有需要发送至服务端的请求参数(空参数值的参数、文件、字节流、sign除外)按照参数名ASCII码从小到大排序(字典序)

注意:

l 参数名ASCII码从小到大排序(字典序);

l 如果参数的值为空不参与签名;

l 文件、字节流不参与签名;

l sign不参与签名;

l 参数名、参数值区分大小写;

② 第二步:将排序后的参数按照URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串strA;

③ 第三步:在strA后面拼接上apiKey得到striSignTemp字符串,将strSignTemp字符串转换为小写字符串后进行MD5运算,MD5运算后得到值作为sign的值传入服务端;

示例(所有参数、参数值均为示例,开发人员参考格式即可):

token:cd171009328172Ad3sc

apiKey:cd13H2ddd22212ds1da

① 第一步(获取到的请求参数并按照参数名ASCII码从小到大排序):

token=cd173309328172Ad322

data={"userName":"18817201899",goods:["addrId":323,{"skuNo":"p12232-023","count":3},{"skuNo":"p12232-013","count":1}]}

timestamp=1507537036

version=v3.6.0

② 第二步(按规则拼接为字符串strA):

token=cd171009328172Ad3sc&data={"userName":"18817201899",goods:["addrId":323,{"skuNo":"p12232-023","count":3},{"skuNo":"p12232-013","count":1}]}timestamp=1507537036&version=v3.6.0

③ 第三步(生成sign):

1)待签名字符串strSignTemp:

token=cd171009328172Ad3sc&data={"userName":"18817201899",goods:["addrId":323,{"skuNo":"p12232-023","count":3},{"skuNo":"p12232-013","count":1}]}timestamp=1507537036&version=v3.6.0cd13H2ddd22212ds1da

2)转换为小写字符串

strtolower()

3)MD5加密后的密文

6D556D52822658FD47F7FE362544CEE1

/*
* @desc 签名函数
* @param $paramArr 系统参数
* @param $apiKey 私钥
* @return string 返回签名
*/
private function createSign ($paramArr,$apiKey) {
ksort($paramArr);
$sign=''; foreach ($paramArr as $key => $val) {
if ($key != '' && $val != '') {
$sign .= $key."=".$val."&";
}
}
$sign=rtrim($sign,"&");
$sign.=$apiKey;
$sign=strtolower($sign);
$sign = md5($sign);
return $sign;
}

(4)拒绝重复调用:客户端第一次访问时,将签名sign存放到缓存服务器中,超时时间设定为跟时间戳的超时时间一致,二者时间一致可以保证无论在timestamp限定时间内还是外 URL都只能访问一次。如果有人使用同一个URL再次访问,如果发现缓存服务器中已经存在了本次签名,则拒绝服务。如果在缓存中的签名失效的情况下,有人使用同一个URL再次访问,则会被时间戳超时机制拦截。这就是为什么要求时间戳的超时时间要设定为跟时间戳的超时时间一致。拒绝重复调用机制确保URL被别人截获了也无法使用(如抓取数据)。

/**
* @desc 限制请求接口次数
* @return bool
*/
private function ask_count(){
$client_ip = $this->sys_get_client_ip();
$ask_url = $this->sys_GetCurUrl();
//限制次数
$limit_num = C('api_ask_limit',null,);
//有效时间内,单位:秒
$limit_time = C('api_ask_time');
$now_time = time();
$valid_time = $now_time - $limit_time;
$ipwhere['creatime'] = array('EGT',date('Y-m-d H:i:s',$valid_time));
$ipwhere['ip_name'] = $client_ip;
$ipwhere['ask_url'] = $ask_url;
$check_result = M('log_ip_ask')->where($ipwhere)->count();
if($check_result !==''){
if($check_result >= $limit_num){
E('已经超出了限制次数!');
}
}
//执行插入
$add_data = array(
'ip_name'=>$client_ip,
'ask_url'=>$ask_url,
'creatime'=>date('Y-m-d H:i:s',time())
);
$result = M('log_ip_ask')->data($add_data)->add();
if($result===false){
E('写入记录失败!');
}
return true;
}
/**
* 获取客户端IP地址
* @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字
* @param boolean $adv 是否进行高级模式获取(有可能被伪装)
* @return mixed
*/
private function sys_get_client_ip($type = ,$adv=false) {
$type = $type ? : ;
static $ip = NULL;
if ($ip !== NULL) return $ip[$type];
if($adv){
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$pos = array_search('unknown',$arr);
if(false !== $pos) unset($arr[$pos]);
$ip = trim($arr[]);
}elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
}elseif (isset($_SERVER['REMOTE_ADDR'])) {
$ip = $_SERVER['REMOTE_ADDR'];
}
}elseif (isset($_SERVER['REMOTE_ADDR'])) {
$ip = $_SERVER['REMOTE_ADDR'];
}
// IP地址合法验证
$long = sprintf("%u",ip2long($ip));
$ip = $long ? array($ip, $long) : array('0.0.0.0', );
return $ip[$type];
} /**
* @desc php获取当前访问的完整url地址
* @return string
*/
private function sys_GetCurUrl() {
$url = 'http://';
if (isset ( $_SERVER ['HTTPS'] ) && $_SERVER ['HTTPS'] == 'on') {
$url = 'https://';
}
if ($_SERVER ['SERVER_PORT'] != '') {
$url .= $_SERVER ['HTTP_HOST'] . ':' . $_SERVER ['SERVER_PORT'] . $_SERVER ['REQUEST_URI'];
} else {
$url .= $_SERVER ['HTTP_HOST'] . $_SERVER ['REQUEST_URI'];
}
return $url;
}

非法ip限制访问,此处的限制一般用在服务器间的接口调用做此限制

    // 允许访问的IP列表
private $ip_allow = array(
'111.11.111.111', // 局域网ip
'111.11.111.112', // 任务服务器
'111.11.111.113', // 代理IP
); /**
* @desc 非法IP限制访问
* @param array $config
* @return bool
*/
private function illegalip(){
if(!$this->ip_limit){
return true;
}
$remote_ip = get_client_ip();
if(in_array($remote_ip, $ip_allow)){
return true;
}
return false;
}

参考链接:https://www.jianshu.com/p/c6518a8f4040

php接口安全设计浅谈的更多相关文章

  1. 浅谈Android系统进程间通信(IPC)机制Binder中的Server和Client获得Service Manager接口之路

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6627260 在前面一篇文章浅谈Service ...

  2. 示例浅谈PHP与手机APP开发,即API接口开发

    示例浅谈PHP与手机APP开发,即API接口开发 API(Application Programming Interface,应用程序接口)架构,已经成为目前互联网产品开发中常见的软件架构模式,并且诞 ...

  3. 浅谈Java中接口与抽象类的异同

    浅谈Java中接口与抽象类的异同 抽象类和接口这两个概念困扰了我许久,在我看来,接口与抽象类真的十分相似.期间也曾找过许许多多的资料,参考了各路大神的见解,也只能是简简单单地在语法上懂得两者的区别.硬 ...

  4. 浅谈Java接口(Interface)

    浅谈Java接口 先不谈接口,不妨设想一个问题? 如果你写了个Animal类,有许多类继承了他,包括Hippo(河马), Dog, Wolf, Cat, Tiger这几个类.你把这几个类拿给别人用,但 ...

  5. 浅谈WebService的版本兼容性设计

    在现在大型的项目或者软件开发中,一般都会有很多种终端, PC端比如Winform.WebForm,移动端,比如各种Native客户端(iOS, Android, WP),Html5等,我们要满足以上所 ...

  6. iOS开发之浅谈MVVM的架构设计与团队协作

    今天写这篇博客是想达到抛砖引玉的作用,想与大家交流一下思想,相互学习,博文中有不足之处还望大家批评指正.本篇博客的内容沿袭以往博客的风格,也是以干货为主,偶尔扯扯咸蛋(哈哈~不好好工作又开始发表博客啦 ...

  7. 浅谈Angular的 $q, defer, promise

    浅谈Angular的 $q, defer, promise 时间 2016-01-13 00:28:00  博客园-原创精华区 原文  http://www.cnblogs.com/big-snow/ ...

  8. 浅谈Hybrid技术的设计与实现第三弹——落地篇

    前言 接上文:(阅读本文前,建议阅读前两篇文章先) 浅谈Hybrid技术的设计与实现 浅谈Hybrid技术的设计与实现第二弹 根据之前的介绍,大家对前端与Native的交互应该有一些简单的认识了,很多 ...

  9. 浅谈Hybrid技术的设计与实现第二弹

    前言 浅谈Hybrid技术的设计与实现 浅谈Hybrid技术的设计与实现第二弹 浅谈Hybrid技术的设计与实现第三弹——落地篇 接上文:浅谈Hybrid技术的设计与实现(阅读本文前,建议阅读这个先) ...

随机推荐

  1. HBase 2.1.3 集群 web 报错InvalidProtocolBufferException 解决方法

    搭建好HBase 集群后,各种后台进程都正常,搭建手册参考: Hbase 2.1.3 集群搭建手册https://www.cndba.cn/dave/article/3322 但是通过web访问,却报 ...

  2. 大数据计算引擎之Flink Flink CEP复杂事件编程

    原文地址: 大数据计算引擎之Flink Flink CEP复杂事件编程 复杂事件编程(CEP)是一种基于流处理的技术,将系统数据看作不同类型的事件,通过分析事件之间的关系,建立不同的时事件系序列库,并 ...

  3. combobox实现模糊搜索匹配

    如图,输入关键字,进行匹配检索: 这里使用的是combobox组合框,对于combobox的创建可以使用<input>输入框,也可以使用<select>下拉选 HTML代码: ...

  4. SPOJ QTREE Query on a Tree【树链剖分模板题】

    树链剖分,线段树维护~ #include <cstdio> #include <cstring> #include <iostream> #include < ...

  5. 微权获取openid信授

    (1)首页要有一个自己的微信测试号的appid和秘钥 (2)公司里都是后台传code(接口),获取openid(接口) 请求code接口:/Wechat/GetUserInfo/getCode //判 ...

  6. 25 JavaScript对象原型&ES5新的对象方法

    JavaScript对象原型 所有JavaScript对象都从原型继承对象和方法 日期对象继承自Date.prototype,数组继承自Array.prototype,对象构造器新建的对象Person ...

  7. java 8 list的stream操作 list中的对象中的某一个成员取出转为该成员的list,以及对象过滤,筛选某个属性后的成员

    取成员属性list List<String> configList = codeEntityList.stream().map(t -> t.getName()).distinct( ...

  8. 安装树莓派实验的Pi 仪表盘

    1.简介 树莓派仪表盘网址:Pi Dashboard (Pi 仪表盘) - MAKER 趣无尽  http://maker.quwj.com/project/10 Pi Dashboard (Pi 仪 ...

  9. 吴裕雄--天生自然Numpy库学习笔记:NumPy 副本和视图

    副本是一个数据的完整的拷贝,如果我们对副本进行修改,它不会影响到原始数据,物理内存不在同一位置. 视图是数据的一个别称或引用,通过该别称或引用亦便可访问.操作原有数据,但原有数据不会产生拷贝.如果我们 ...

  10. MSE-初始化MSE

    MSE(Mobility Services Engine) Cisco MSE可以配合无线实现很多功能,MSE的功能简单概括有: 1.基本位置服务捕获并聚合关键网络信息,例如设备位置,RF频谱详细信息 ...