随着公司业务渐渐壮大,为了降低系统之间的耦合度,提高系统与系统之间的协作效率。可能部分业务将 采用 api 方式进去数据操作,但是基于 HTTP 协议方式传递数据会涉及到下列三个基础问题:
数据提交者是否合法?
数据传递期间是否被篡改?
数据是否被多次提交?
如何解决以上三个问题呢? so easy !
一些常见的 SDK 里面已经涉及到这些问题,细心的童靴已经发现了解决方案。
解决数据提交是否合法、数据是否被篡改
客户端和服务端约定签名算法,客户端生成签名作为数据传递的一个字段,服务端拿到签名进行合法性验证。常见的签名步骤如下:
服务端提供方给出app_id和app_secret
- 客户端端根据app_id和app_secret以及请求参数,按照一定算法生成签名sign
- 服务端进行验证签名
具体以支付宝签名为例子(PHP版本,客户端)
<?php
class sign{
/**
* 生成要请求给支付宝的参数数组
* @param $para_temp 请求前的参数数组
* @return 要请求的参数数组
*/
function buildRequestPara($para_temp, $app_secret = '', $sign_type = 'MD5') {
//除去待签名参数数组中的空值和签名参数
$para_filter = $this->paraFilter($para_temp);
//对待签名参数数组排序
$para_sort = $this->argSort($para_filter);
//生成签名结果
$mysign = $this->buildRequestMysign($para_sort, $app_secret);
//签名结果与签名方式加入请求提交参数组中
$para_sort['sign'] = $mysign;
$para_sort['sign_type'] = strtoupper(trim($sign_type));
return $para_sort;
}
/**
* 获取返回时的签名验证结果
* @param $para_temp 通知返回来的参数数组
* @param $sign 返回的签名结果
* @return 签名验证结果
*/
function getSignVeryfy($para_temp, $sign, $app_secret = '', $sign_type = 'MD5') {
//除去待签名参数数组中的空值和签名参数
$para_filter = $this->paraFilter($para_temp);
//对待签名参数数组排序
$para_sort = $this->argSort($para_filter);
//把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
$prestr = $this->createLinkstring($para_sort);
$isSgin = false;
switch (strtoupper(trim($sign_type))) {
case "MD5" :
$isSgin = $this->md5Verify($prestr, $sign, $app_secret);
break;
default :
$isSgin = false;
}
return $isSgin;
}
/**
* 生成签名结果
* @param $para_sort 已排序要签名的数组
* return 签名结果字符串
*/
function buildRequestMysign($para_sort, $app_secret, $sign_type = 'MD5') {
//把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
$prestr = $this->createLinkstring($para_sort);
$mysign = "";
switch (strtoupper(trim($sign_type))) {
case "MD5" :
$mysign = $this->md5Sign($prestr, $app_secret);
break;
default :
$mysign = "";
}
return $mysign;
}
/**
* 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
* @param $para 需要拼接的数组
* return 拼接完成以后的字符串
*/
function createLinkstring($para) {
$arg = "";
while (list ($key, $val) = each ($para)) {
$arg.=$key."=".$val."&";
}
//去掉最后一个&字符
$arg = substr($arg,0,count($arg)-2);
//如果存在转义字符,那么去掉转义
if(get_magic_quotes_gpc()){$arg = stripslashes($arg);}
return $arg;
}
/**
* 签名字符串
* @param $prestr 需要签名的字符串
* @param $key 私钥
* return 签名结果
*/
function md5Sign($prestr, $key) {
$prestr = $prestr . $key;
return md5($prestr);
}
/**
* 验证签名
* @param string $prestr 需要签名的字符串
* @param string $sign 签名结果
* @param string $key 私钥
* return boolean 签名结果
*/
function md5Verify($prestr, $sign, $key) {
$prestr = $prestr . $key;
$mysgin = md5($prestr);
if($mysgin == $sign) {
return true;
}
else {
return false;
}
}
/**
* 除去数组中的空值和签名参数
* @param $para 签名参数组
* return 去掉空值与签名参数后的新签名参数组
*/
function paraFilter($para) {
$para_filter = array();
while (list ($key, $val) = each ($para)) {
if($key == "sign" || $key == "sign_type" || $val == "")continue;
else $para_filter[$key] = $para[$key];
}
return $para_filter;
}
/**
* 对数组排序
* @param $para 排序前的数组
* return 排序后的数组
*/
function argSort($para) {
ksort($para);
reset($para);
return $para;
}
}
include './httplib.class.php';// 基础的 curl
include './sign.class.php';
$app_id = 'demo';
$app_secret = 'your secret';
$time = time();
$data = [
'uid' => '1',
'name' => 'kevin',
'age' => '25',
'fid' => '1',
'_timestamp' => $time,
];
$sign = new sign();
$params = $sign->buildRequestPara($data, $app_secret);
$http = new httplib();
$request_url = 'http://127.0.0.1/sign_response.php';// 服务端接口地址
$http->request($request_url, $params);
服务端接口处理
include './sign.class.php';
$app_id = 'demo';
$app_secret = 'your secret';
$sign = new sign();
$data = $_POST; // 未做安全处理
if(!isset($data['sign']) || empty($data['sign']))
{
echo '缺少签名参数';exit;
}
$sign = new sign();
if($sign->getSignVeryfy($data, $data['sign'], $app_secret)) {
echo '签名认证成功,继续你的业务逻辑';
} else {
echo '签名失败';
}
运行结果
关于多次提交客服端可相对判定
- 上面的栗子可以根据一个时间戳字段 _timestamp 在相对时间内请求多次。
if(!isset($data['_timestamp']) || empty($data['_timestamp']))
{
echo '缺少时间参数';exit;
}
$nowtime = time();
// 120 秒 即 2分钟内 请求是有效的,客服端和服务端时间可能有差异,此判断不是客观的,根据时间情况参订
if(($nowtime-intval($data['_timestamp'])) > 120)
{
echo '缺少时间参数';exit;
}
- 以上只是相对判定,服务端当然也可以保存客户端请求,下次进行判定,当然成本很高。服务端也可以生成 一次性token,客户端得到此token传递数据时候带上token 服务端再次验证token的有效性