接口的安全性主要围绕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. java 倒入一篇文章,显示每个字母的出现概率,从大到小排序

    package com_1; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; ...

  2. 1018 Public Bike Management (30分) (迪杰斯特拉+dfs)

    思路就是dijkstra找出最短路,dfs比较每一个最短路. dijkstra可以找出每个点的前一个点, 所以dfs搜索比较的时候怎么处理携带和带走的数量就是关键,考虑到这个携带和带走和路径顺序有关, ...

  3. SpringBoot+mongoDB实现id自增

    这段时间给朋友做了一个微信小程序,顺便练习一下spring boot,虽然项目使用的是JPA+MySQL,但是好奇尝试了一下MongoDB实现自增ID,虽然MongoDB很少有自增ID的需求(在分布式 ...

  4. [1/100]Python安装

    Python官方版本下载地址: https://www.python.org/downloads/release (上图为3.8.2网络安装版) 默认安装: pip Python安装模块 tkinte ...

  5. 【Hibernate 检索策略】

    HibernateDemo2 public class HibernateDemo2 { //演示批量抓取 @Test public void testSelect3() { SessionFacto ...

  6. python __双划线 参数

    ''' >>> Class1.__doc__ # 类型帮助信息 'Class1 Doc.' >>> Class1.__name__ # 类型名称 'Class1' ...

  7. 爬虫,工具 - Splash

    What is it? Splash is a javascript rendering service. It's a lightweight web browser with an HTTP AP ...

  8. 喵星之旅-狂奔的兔子-redis安装

    一.前置条件 服务器版本CentOS-8-x86_64-1905-dvd1,在此版本上安装最新版redis.centos7以上版本都可以,不建议6以前的版本. 二.下载redis,并上传到服务器 登录 ...

  9. 开关电源ac-dc推荐电路

    在使用AC-DC电源模块SA系列时,如果碰到对模块的输出纹波噪声要求较高或对EMC要求严格的场合,应对模块进行必要的滤波处理使到满足不同环境的特殊要求,以下推荐一滤波电路供参考: 图中各元件的说明:1 ...

  10. CDH

    CDH 1.CDH简介 CDH 2.Cloudera Manager的安装     软件下载地址: 链接:https://pan.baidu.com/s/1C5HpiVEOtH_4PjylyJaXvA ...