美文网首页
开放平台应用接口签名认证

开放平台应用接口签名认证

作者: 今年五年级 | 来源:发表于2020-11-23 16:30 被阅读0次

http接口签名机制
http接口的内容是明文传输,为了提高传输过程参数的防篡改性,签名sign的方式是目前比较常用的方式。例如腾讯开放平台api
https://wiki.open.qq.com/wiki/%E8%85%BE%E8%AE%AF%E5%BC%80%E6%94%BE%E5%B9%B3%E5%8F%B0%E7%AC%AC%E4%B8%89%E6%96%B9%E5%BA%94%E7%94%A8%E7%AD%BE%E5%90%8D%E5%8F%82%E6%95%B0sig%E7%9A%84%E8%AF%B4%E6%98%8E

首先给需要调用我们开放接口的开发者分发appId(开发者标识,确保唯一)和appSecret(用于接口加密,确保不易被穷举,生成算法不易被猜测)

签名过程:

  1. 将所有参数(注意是所有参数),除去sign本身,以及值是空的参数,按参数名字母升序排序。
  2. 然后把排序后的参数按参数1值1参数2值2…参数n值n(这里的参数和值必须是传输参数的原始值,不能是经过处理的,如不能将"转成”后再拼接)的方式拼接成一个字符串。
  3. 把分配给接入方的验证密钥key拼接在第2步得到的字符串前面。
    第2步: 在上一步得到的字符串前面加上验证密钥(appSecret),然后计算md5值,得到32位字符串,然后转成大写.
  4. 计算第3步字符串的md5值(32位),然后转成大写,得到的字符串作为sign的值。

举例:
假设传输的数据是http://www.xxx.com/interface.aspx?sign=sign_value&p2=v2&p1=v1&method=cancel&p3=&pn=vn(实际情况最好是通过post方式发送),
其中sign参数对应的sign_value就是签名的值。

  1. 拼接字符串,首先去除sign参数本身,然后去除值是空的参数p3,剩下p2=v2&p1=v1&method=cancel&pn=vn,然后按参数名字符升序排序,method=cancel&p1=v1&p2=v2&pn=vn.
  2. 然后做参数名和值的拼接,最后得到methodcancelp1v1p2v2pnvn
  3. 在上面拼接得到的字符串最后面加上验证密钥appSecret,我们假设是abc,得到新的字符串methodcancelp1v1p2v2pnvnabc
  4. 然后将这个字符串进行md5计算,假设得到的是abcdef,然后转为大写,得到ABCDEF这个值即为sign签名值。

注意,计算md5之前请确保接口与接入方的字符串编码一致,如统一使用utf-8编码或者GBK编码,如果编码方式不一致则计算出来的签名会校验失败。

优点:上面的加签过程如果请求参数被人拿走,不会有问题,其他人永远也拿不到appSecret因为appSecret是不传递的,只用来生成签名。

缺点:但是无法避免有人获得了请求的完整地址,然后继续使用相同的参数来重复请求就可以获取数据,为此我们需要添加时间戳校验来保证请求的唯一性,就是对应请求只能使用一次,这样就算别人拿走了请求的完整链接也是无效的,我们引入了下面的时间戳+nonce方案

timestamp+nonce方案

nonce指唯一的随机字符串,用来标识每个被签名的请求。通过为每个请求提供一个唯一的标识符,服务器能够防止请求被多次使用(记录所有用过的nonce以阻止它们被二次使用)。拥有服务器nonce的原因是为了防止man中间人攻击,以防攻击者捕获有效的服务器响应,并尝试将其重播给客户端

然而,对服务器来说永久存储所有接收到的nonce的代价是非常大的。可以使用timestamp来优化nonce的存储。

在请求参数中,我们加入时间戳 :timestamp,和一个随机数nonce(nonce指唯一的随机字符串,用来标识每个被签名的请求),同样,时间戳和nonce作为请求参数之一,也加入sign算法中进行加密。新请求地址:
http://www.xxx.com/interface.aspx?sign=sign_value&p2=v2&p1=v1&method=cancel&p3=&pn=vn&nonce=v4&timestamp=1606103174660

实例

    private static final String SIGN_KEY = "signature";
    private static final String TIMESTAMP_KEY = "_timestamp";
    private static final String APPID_KEY = "appId";
    private static final String NONCE_KEY = "nonce";

    private static final String REDIS_NONCE_KEY = "nonce_";
    private static final String REDIS_APPSESCRET_KEY_ = "appSecret_";
    private static final int REQUEST_EXPIRE_TIME = 5 * 60 * 1000;

    @Resource
    private RedisTemplate redisTemplate;

    public R verifySign(HttpServletRequest request) {
        try {
            /* protected ParameterMap<String, String[]> parameterMap; */
            Map<String, String> params = MapUtil.convertMap(request);
            String signature = params.get(SIGN_KEY);
            String timestamp = params.get(TIMESTAMP_KEY);
            String appIdMix = params.get(APPID_KEY);
            String paramNonce = params.get(NONCE_KEY);
            if (StringUtils.isBlank(signature)
                    || StringUtils.isBlank(timestamp) || !StringUtils.isNumeric(timestamp)
                    || StringUtils.isBlank(appIdMix) || StringUtils.isBlank(paramNonce))

                return R.error(500100, "参数验证异常");

            Long appId = Utils.decryptUserId(appIdMix);
            String appSecret = (String) redisTemplate.opsForValue().get(REDIS_APPSESCRET_KEY_ + appId);
            if (StringUtils.isBlank(appSecret))
                return R.error(500101, "未授权的调用来源");

            long timestampL = Long.parseLong(timestamp);
            if ((System.currentTimeMillis() - timestampL) > REQUEST_EXPIRE_TIME)
                return R.error(500102, "请求已超时");
         
            String isMember = (String) redisTemplate.opsForValue().get(REDIS_NONCE_KEY + paramNonce);
            if (StringUtils.isNotBlank(isMember))
                return R.error(500103, "请求已经被处理, 不可再次请求");

            params.remove(SIGN_KEY);
            boolean verify = VerifyUtils.verify(params, appSecret, signature);
            if (!verify)
                return R.error(500104, "签名校验失败");

            redisTemplate.opsForValue().set(REDIS_NONCE_KEY + paramNonce, paramNonce,
                    (System.currentTimeMillis() - timestampL), TimeUnit.MILLISECONDS);
            return R.ok();
        } catch (Exception e) {

            return R.error(e.getMessage());

        }

    }

推荐:
https://www.cnblogs.com/codeon/p/6123863.html

相关文章

网友评论

      本文标题:开放平台应用接口签名认证

      本文链接:https://www.haomeiwen.com/subject/lrvmiktx.html