接口的安全性主要围绕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. SharePoint 获取服务器场管理员密码

    前言 这还是很久以前发生的故事(你也可以说事故),公司新来的小朋友帮客户运维,然后,因为客户要改场管理员密码,这个很简单啊,我们有密码变更的文档.小朋友分分钟就帮客户把密码更新了,然后,就去干别的了. ...

  2. PTA喊山

    喊山 喊山,是人双手围在嘴边成喇叭状,对着远方高山发出“喂—喂喂—喂喂喂……”的呼唤.呼唤声通过空气的传递,回荡于深谷之间,传送到人们耳中,发出约定俗成的“讯号”,达到声讯传递交流的目的.原来它是彝族 ...

  3. Ansible playbooks(任务、角色、模板、变色器、)

    playbooks配置文件: [root@ansible ~]# vim /etc/ansible/hosts [test01] 192.168.200.114 [test02] 192.168.20 ...

  4. SpringCloud全家桶学习之路由网关----Zuul(六)

    一.Zuul概述 (1)Zuul是什么? Zuul包含了对请求的路由和过滤的两个最主要的功能,其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础:而过滤功能则负责对请求的 ...

  5. 15 FFT及其框图实现

    FFT及其框图实现 \(FFT\)的全称为快速傅里叶变换,但是\(FFT\)并不是一种变换,而是实现\(DFT\)的一种快速算法.当\(N\)比较大时,使用\(FFT\)可大大减少进行\(DFT\)变 ...

  6. DAY1小题

    F 求逆序对的板子题 #include<cstdio> #define ll long long using namespace std; ; ll a[maxn],r[maxn],n; ...

  7. laravel 语言包拓展

    laravel 使用make:auth生成用户认证,登录表单是英文版本的,打开模板文件(resources/views/auth/login.blade.php),此模板文件是我们刚刚使用 make: ...

  8. Plastic Sprayers Manufacturer - Ingenious Design Of Spray Plastic Bottle

    Plastic bottles are now an indispensable container in life. Plastic bottles will appear in all aspec ...

  9. opencv python:ROI 与 泛洪填充

    提取ROI区域,处理然后放回去: 泛洪填充 测试代码:显示一张图像,鼠标点击之后,会从该点开始进行填充,显示填充后的结果图像 注:二值图像的填充需要使用选项:cv2.FLOODFILL_MASK_ON ...

  10. python splash scrapy

    python splash scrapy 1.      前言 slpash是一个渲染引擎,它有自己的api,可以直接访问splash服务的http接口,但也有对应的包python-splash方便调 ...