zoukankan      html  css  js  c++  java
  • 对接第三方服务引起的小思考-回调和Sign算法

    背景

    ​ 最近在对接一个同事写的支付公用模块,然后对第三方服务引起一两个小思考。

    思考

    回调

    来看看我们同事是如何做回调的。

    首先,请求支付接口的时候,将回调URL作为请求body的一个参数[不加密]。

    {
        "xxx": "xxx",
        "xxx": "xx",
        "xxxxx": "xxxxx",
        "callBackUrl": "http://www.baidu.com"
    }
    

    然后,当第三方支付服务成功后,支付服务会对上面的回调URL发出一次http请求,然后固定请求体带上以下参数:

    {
        "code": 0,
        "充值订单号": "3333333333333333",
        "支付订单号": 361504175005106176,
        "支付状态": "SUCCESS",
        "充值数额": 88.00,
        "subject": "游戏",
        "gmtCreate": "2019-08-21 10:49:21",
        "gmtPayment": "2019-08-21 10:49:21",
    }
    

    到这里,我们可以看到

    1、回调url是没有加密的,那么如果有黑客拦截到此数据,就可以拿到此url不断地攻击服务器了。

    2、回调时的参数是支付服务方固定的,而且没有做到扩展,调用方不能增加自定义参数。

    那么比较好的解决方案有什么呢,我们可以参考一下阿里的对象存储OSS是怎么做的。
    Callback
    用户只需要在发送给 OSS 的请求中携带相应的 Callback 参数,即能实现回调。
    Callback包含下面字段(json格式):

    {
        "callbackUrl":"121.101.166.30/test.php",  // 回调url
        "callbackHost":"oss-cn-hangzhou.aliyuncs.com", // 回调host
        "callbackBody":"{"mimeType":${mimeType},"size":${size}}", //回调请求体
        "callbackBodyType":"application/json" // 回调请求体类型
    }
    

    ​ 然后将Callback对象转成Json字符串,再进行Base64编码,最后将Base64编码后的字符串作为一个请求参数即可。最后的请求体可能为如下:

    {
        "xxx": "xxx",
        "xxx": "xx",
        "xxxxx": "xxxxx",
        "callBack": "23jdf7adf8gfasg98g78a9dgda"
    }
    

    ​ 这样的话,我们可以看到,这就算是被别人拦截了,第一眼看过去肯定是懵逼的。但是呢,只要猜到是base64编码的,反编码后还是可以看到里面的东西。如果对安全性的要求是极致的,大家还可以加上加密算法[可解密],千万不要加密完不能倒退回来。。。

    Sign算法

    ​ 一般的Sign算法,将请求参数以key1=value2&key2=value2的格式拼接起来,然后再拼接header的参数,最后的格式为:hKey1=hValue2&hKey2=hValue2&data=xxxx,而data的值就是上面请求参数拼接后的字符串,最后进行加密。
    ​ 我们可以想象,如果客户端的拼接顺序和服务端拼接的顺序不一致,那么最后加密后的字符串肯定是不相等,那么最后必须是服务端返回一句话:签名不合法。
    下面,我们看一下一开始SignUtil中对请求参数的拼接函数:

    /**
         * 将参数进行拼接
         * 返回结果为: key1=value1&key2=value2&key3=value3
         *
         * @param map Map参数
         * @return String
         */
    public static String mapToString(Map<String, Object> map) {
        StringBuffer builder = new StringBuffer();
        map.forEach((key, value) -> builder.append(key).append("=").append(value).append("&"));
        builder.deleteCharAt(builder.length() - 1);
        return builder.toString();
    }
    

    ​ 我们可以看到,是遍历Map来进行拼接。Map如果以顺序分类,可以分为两大类:一种是有序(例如LinkedHashMap和TreeMap),一种是无序(HashMap)。如果支付服务提供的SignUtil提供的拼接方法如上,那么就是说,我们开发者并不知道支付服务端是传入哪种类型的Map来进行拼接;这时候如果支付服务端使用的是TreeMap,而我们自己在不知道的前提下使用了比较常用的HashMap,那么这时候就出大问题了,签名永远是错误的。

    解决方法(支付服务方提供SignUtil):

    1、第三方服务的开发文档必须提醒开发者该传入的Map类型。

    ​ 拼接方法传入参数还是可以为Map,因为利用了泛型这样比较通用,但是呢文档该注重提醒开发者是传入有序的还是无序的实现类。

    2、拼接方法传入参数指定Map类型,甚至具体到实现类。

    ​ 虽然说这样没有使用到泛型,显得不够通用,可是正因为如此,能强制规定开发者的输入,能避免非常多不必要的坑。

    /**
         * 将参数进行拼接
         * 返回结果为: key1=value1&key2=value2&key3=value3
         *
         * @param map HashMap参数
         * @return String
         */
    public static String mapToString(HashMap<String, Object> map) {
        StringBuffer builder = new StringBuffer();
        map.forEach((key, value) -> builder.append(key).append("=").append(value).append("&"));
        builder.deleteCharAt(builder.length() - 1);
        return builder.toString();
    }
    

    总结

    ​ 如果是做提供第三方服务的程序猿,我们必须考虑到接口的通用性,还有更重要的是准确性;然后还有就是文档应该简洁而不简单,能让开发者看文档即可一次搞定对接,而不是浪费不必要的时间去尝试,去揣测第三方服务究竟是如何设计的。

  • 相关阅读:
    SDOI2017 R2泛做
    类似静态区间逆序对的题的一些做法
    友链&&日记
    注意事项以及一些奇怪的姿势
    关于各种算法以及好的blog的整理(持续更新)
    PKUSC2019游记
    洛谷P5398 [Ynoi2018]GOSICK(二次离线莫队)
    洛谷P4887 第十四分块(前体)(二次离线莫队)
    [51nod]1678 lyk与gcd(莫比乌斯反演)
    LOJ#557. 「Antileaf's Round」你这衣服租来的吗(FHQ Treap+珂朵莉树)
  • 原文地址:https://www.cnblogs.com/Howinfun/p/11612643.html
Copyright © 2011-2022 走看看