首页 > 服务器 正文
关于API接口的签名和权鉴,你知道多少?

时间:2022-11-27 21:53:47 阅读: 评论: 作者:

  一、背景

  最近在做第三方接口对接的一些工作,考虑到交互的安全性也为了不让数据在传输中“裸奔”,所以签名和权鉴是必不可少的了。

  二、关于API接口权鉴

  2.1、从登录验证说起

  在我们内部项目之间进行的接口调用中一般会用到这种:用户登录->生成token并保存->接口请求验证token,这里也可以把token做成全局的用以单点登录。

  2.2、说说使用token验证

  ① 但是如果我们要对接第三方接口或者向第三方提供接口时,这个模式就使用得比较少一些了。首先,token携带在请求中很多时候已经是一个明文传输了,有心的话可以直接进行数据提取然后做一些其他事情(做过爬虫的应该都很熟练哈)。

  ② 在第三方接口对接时更多的是以加盟或绑定的形式为主,在接口调用之前很少会做登录的动作,而且处于安全性考虑这种方式的安全性也有待进一步提升。

  2.3、解决方案

  其实这个问题已经是有比较完备的解决方案了得:双方约定一个公钥,一个私钥然后按照特定的算法(MD5、SHA256)进行加密生成一个签名。使用方在接口调用时携带签名及其他数据,提供方在接收到调用时也按照约定进行一次签名的生成,最后会比较两方的数据,如果两方的签名一致则请求检测初步通过,不符合时则拒绝。

  三、我之项目解决方案

  3.1、整体思路

  使用特定的签名算法且使用双方都进行生成(其参数包含公钥、时间戳、携带参数)。在我这边接收到请求时先去检测公钥是否在我方的约定池中存在,然后校验签名一致性、有效时间,最后是参数合法性检测。其中在中间任何一个环节出现问题即抛出对应回执信息。

  PS:部分参数可参与签名的算法

  3.2、签名算法

  在这里面最重要的就是签名算法了,其操作步骤如下:

  ① 使用Map接收参数,然后对按照其中的非空参数名的ACCII码从小到大进行排序

  ② 使用URL键值对的形式(key1=value1&key2=value2...)进行字符串拼接

  ③ 在上述生成的字符串上面拼接私钥得到新的字符串signStr,并对signStr进行SHA256运算得到signShsStr

  ④ 最后把signShaStr转换成大写,得到最终的签名

  四、相关核心代码

  4.1、signBuildUtil

   /** * @author XA * date 2020/9/10 14:11 * description XXX签名业务操作实现 * params * return */ @Component public class SignBuildUtil { /** * 功能描述: 签名生成 * Param: [paramMap, secretKey] * Return: java.lang.String */ public String generateSign(Map<String, String> paramMap, String secretKey) { Map<String, String> acsMap = sortMapByKey(paramMap); StringBuilder sb = new StringBuilder(); for (Map.Entry<String, String> entry : acsMap.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); if (this.hasText(value)) { sb.app(key).app("=").app(value).app("&"); } } sb.app("key=").app(secretKey); return DigestUtils.sha256HexDigest(sb.toString()).toUpperCase(); } /** * 功能描述: 签名校验(接收入参和secrectKey本地生成一份进行数据比对) * Param: [dataMap, secretKey] * Return: boolean */ public boolean checkSign(Map<String, String> dataMap, String secretKey) { Map<String, String> tempMap = new TreeMap<>(dataMap); if (tempMap.containsKey("sign")) { String sign = tempMap.remove("sign"); String correctSign = this.generateSign(tempMap, secretKey); return correctSign.equals(sign); } return false; } /** * 功能描述: 排序 * Param: [map] * Return: java.util.Map<java.lang.String,java.lang.String> */ private static Map<String, String> sortMapByKey(Map<String, String> map) { if (map == null

   map.isEmpty()) { return null; } Map<String, String> sortMap = new TreeMap<>(new MapKeyComparator()); sortMap.putAll(map); return sortMap; } static class MapKeyComparator implements Comparator<String> { @Override public int compare(String str1, String str2) { return str1.compareTo(str2); } } private boolean hasText(String str) { return (hasLength(str) && containsText(str)); } private boolean containsText(CharSequence str) { int strLen = str.length(); for (int i = 0; i < strLen; i++) { if (!Character.isWhitespace(str.charAt(i))) { return true; } } return false; } }

  4.2、SHA256运算

   /** * @author XA * date 2020/9/10 14:09 * description 字符SHA256运算 * params * return */ public class DigestUtils { private static final String SHA256_ALGORITHM_NAME = "SHA-256"; private static final char[] HEX_CHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; public static String sha256HexDigest(String source) { try { MessageDigest messageDigest = MessageDigest.getInstance(SHA256_ALGORITHM_NAME); messageDigest.update(source.getBytes("UTF-8")); return new String(encodeHex(messageDigest.digest())); } catch (NoSuchAlgorithmException

   UnsupportedEncodingException e) { throw new RuntimeException(e); } } private static char[] encodeHex(byte[] bytes) { char[] chars = new char[bytes.length * 2]; for (int i = 0; i < chars.length; i = i + 2) { byte b = bytes[i / 2]; chars[i] = HEX_CHARS[(b >>> 0x4) & 0xf]; chars[i + 1] = HEX_CHARS[b & 0xf]; } return chars; } }

  五、后记

  5.1、在签名中可以让部分重要的参数进行参与

  5.2、使用时间戳并参与签名一方面是增加签名的预测难度也可以作为时间有效性检测的依据

  5.3、最好是有一个良好的文档约定和公钥、私钥的良好维护