相对安全的 restful api - 数据传输篇

随着公司业务渐渐壮大,为了降低系统之间的耦合度,提高系统与系统之间的协作效率。可能部分业务将 采用 api 方式进去数据操作,但是基于 HTTP 协议方式传递数据会涉及到下列三个基础问题:

  1. 数据提交者是否合法?

  2. 数据传递期间是否被篡改?

  3. 数据是否被多次提交?

如何解决以上三个问题呢? 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 '签名失败';
}

运行结果
这里写图片描述


关于多次提交客服端可相对判定

  1. 上面的栗子可以根据一个时间戳字段 _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的有效性
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值