1、背景

我现在有几个公开的接口需要开放给第三方厂商,第三方厂商通过调用我的接口获取数据,由于这些接口放开了登录的拦截,而且是直接暴露在公网上的,因此想给这几个接口加上权限的验证,即加签验签。

2、技术选型

通过某歌和某度查找资料,以及向一些博主、论坛、微信群、QQ群请教…发现接口的加签验签最安全的方法是使用非对称加密的方式,即RSA的公私钥,但这种验签方式需要第三方提供他们的公钥给我方,第三方利用他们的私钥生成签名sign,然后我方使用第三方的公钥验证签名sign,识别调用接口者的身份。这个场景好像有点不符合我的要求,于是我选择了利用MD5加盐的方式,双方使用同一个盐字段加密解密(好像有点不是很安全…)

3、签名规则与流程

  • 现在对原有的接口进行改造,主要是增加了三个参数:
appId:      由我分配给第三方的id,用于确认第三方的身份
timestamp: 当前时间戳,如果没有这个字段,那么就可以拿之前的链接重复访问了
sign:      签名字段,是把第三方的请求参数拼接,再拼接上secret盐字段,经过md5加密得到
  • 我需要给第三方提供:
appId:   由我分配给第三方的id,用户确认第三方的身份
secret: 密钥,作为md5加密的盐,仅作加密使用, 不在请求参数中使用,要是这个泄露了,那么加密也就形同虚设了
uri:    接口在公网的地址
  • 签名规则参考了支付宝开放平台的签名规则:

不包括字节类型参数,如文件、字节流,剔除 sign 字段,剔除值为空的参数;按照第一个字符的键值 ASCII 码递增排序(字母升序排序),如果遇到相同字符则按照第二个字符的键值 ASCII 码递增排序,以此类推;将排序后的参数与其对应值,组合成 参数=参数值 的格式,并且把这些参数用 & 字符连接起来,此时生成的字符串为待签名字符串。

  • 第三方将参数生成的字符串,拼接上secret密钥,经过md5加密,得到用户签名,就可以调用我的接口了:
http://localhost:8081/open/api/xxxxxx?appId=test&timestamp=1616228945&sign=963DA2488CBCBB1E3736D9DA621408DE&参数1=value1&参数2=value2.......
  • 接下来请求到了我这,我这边采用拦截器的方式,对特定接口进行验签

    • 校验分发的appId是否存在
    • 校验时间戳是否过期
    • 与第三方生成签名sign一样,我这边使用同样的方式生成签名,如果我生成的签名和第三方参数中的签名一致,则放开拦截,继续执行方法,否则返回错误信息

4、代码

4.1、Controller参数

@RequestParam String appId,
@RequestParam Long timestamp,
@RequestParam String sign,
......

4.2、自定义拦截器

/*** @author CHY* @date 2021/3/18 15:50* @description 自定义拦截器,拦截公开给第三方的接口,验签*/
public class SignatureInterceptor implements HandlerInterceptor {@Value("${open.api.secret}")private String secret;@Value("#{'${open.api.appIdList}'.split(',')}")private List<String> appIdList;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {String appId = request.getParameter("appId");if (!appIdList.contains(appId)) {throw new DException("appId错误");}String sign = request.getParameter("sign");if (StringUtils.isEmpty(sign)) {throw new DException("签名错误");}String timestamp = request.getParameter("timestamp");// 时间戳要为10位的秒级时间戳if (timestamp.length() != 10) {throw new DException("timestamp时间戳格式错误");};//check时间戳的值是否在当前时间戳前后一30秒以内String currTimestamp = String.valueOf(Instant.now().getEpochSecond());int currTimestampNum = Integer.parseInt(currTimestamp);int verifyTimestampNum = 0;try {verifyTimestampNum =  Integer.parseInt(timestamp);} catch (NumberFormatException e) {throw new DException("timestamp时间戳格式错误");}// 在30秒范围之外,访问已过期if (Math.abs(verifyTimestampNum - currTimestampNum) > 30) {throw new DException("签名已经过期");}Enumeration<?> pNames = request.getParameterNames();Map<String, String> params = new HashMap<>();while (pNames.hasMoreElements()) {String pName = (String) pNames.nextElement();if ("sign".equals(pName)) {continue;}String pValue = request.getParameter(pName);params.put(pName, pValue);}if (sign.equals(SignUtils.getSign(params, secret))) {return true;} else {throw new DException("签名错误");}}
}

4.3、注册拦截器

/*** @author CHY* @date 2020/8/30 1:14* @description 配置*/
@Configuration
public class MvcConfigurer implements WebMvcConfigurer {@Beanpublic SignatureInInterceptor signatureInInterceptor() {return new SignatureInInterceptor();}/*** 注册拦截器*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {LinkedList<String> addPathList = new LinkedList<>();addPathList.add("/open/api/xxxxxxxxxxxxxx");addPathList.add("/open/api/xxxxxxxxxxxxxx");addPathList.add("/open/api/xxxxxxxxxxxxxx");registry.addInterceptor(signatureInInterceptor()).addPathPatterns(addPathList);}/*** 解决跨域问题* @param registry*/@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOrigins("*").allowCredentials(true).allowedMethods("GET", "POST", "PUT", "DELETE","OPTION").allowedHeaders("*").maxAge(3600);}
}

4.4、加密工具类,Main方法测试接口,模拟第三方发送请求

/*** @author CHY* @date 2021/3/18 9:25* @description MD5加密工具类*/
@Slf4j
public class SignMD5Utils {private static final String appId = "xxx";private static final String secret = "xxxxxxxxx";public static void main(String[] args) throws IOException {long nowTimestamp = Instant.now().getEpochSecond();String updateTime = "2020/01/01 10:01:01";String urlUpdateTime = updateTime.replaceAll(" ", "%20");//参数签名测试例子HashMap<String, String> signMap = new HashMap<>();signMap.put("appId", appId);signMap.put("timestamp", String.valueOf(nowTimestamp));signMap.put("updateTime", updateTime);String sign = getSign(signMap, secret);System.out.println("得到签名sign: " + sign);String url = "http://localhost:8081/open/api/xxxxxxxx?" + appId + "&timestamp=" + nowTimestamp +"&sign=" + sign + "&updateTime=" + urlUpdateTime;System.out.println("生成的url: " + url);String resp = doGet(url);System.out.println(resp);}/*** 得到签名* @param params 参数集合,不含密钥secret* @param secret 分配的密钥secret* @return sign 签名*/private static String getSign(Map<String, String> params, String secret) {StringBuilder sb = new StringBuilder();// 先对请求参数去重并排序Set<String> keySet = params.keySet();TreeSet<String> sortSet = new TreeSet<>(keySet);// 将排序后的参数与其对应值,组合成 参数=参数值 的格式,并且把这些参数用 & 字符连接起来,此时生成的字符串为待签名字符串for (String key : sortSet) {String value = params.get(key);sb.append(key).append("=").append(value).append("&");}sb.append("secret=").append(secret);byte[] md5Digest;// Md5加密得到signmd5Digest = getMd5Digest(sb.toString());return byte2hex(md5Digest);}/*** 获取md5信息摘要* @param data 需要加密的字符串* @return bytes 字节数组*/private static byte[] getMd5Digest(String data) {byte[] bytes = null;try {MessageDigest md = MessageDigest.getInstance("MD5");bytes = md.digest(data.getBytes(StandardCharsets.UTF_8));} catch (GeneralSecurityException gse) {log.error("生成签名错误", gse);}return bytes;}/*** 将字节数组转化为16进制* @param bytes 字节数组* @return sign 签名*/private static String byte2hex(byte[] bytes) {StringBuilder sign = new StringBuilder();for (byte aByte : bytes) {String hex = Integer.toHexString(aByte & 0xFF);if (hex.length() == 1) {sign.append("0");}sign.append(hex.toUpperCase());}return sign.toString();}/*** java发送http请求*/private static String doGet(String url) throws IOException {// 返回结果集StringBuilder result = new StringBuilder();// 输入流BufferedReader in = null;try {// 链接URLURL netUrl = new URL(url);// 创建链接HttpURLConnection conn = (HttpURLConnection) netUrl.openConnection();// 连接服务器conn.connect();// 取得输入流,并使用Reader读取,设定字符编码in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));String line;//读取返回值,直到为空while ((line = in.readLine()) != null) {result.append(line);}} catch (IOException e) {throw new IOException("连接失败", e);} finally {if (in != null) {try {in.close();} catch (IOException e) {throw new IOException("生成签名错误", e);}}}return result.toString();}
}

5、参考资料

https://juejin.cn/post/6854573210411892749#heading-0

https://opendocs.alipay.com/open/291/sign

https://blog.csdn.net/sunzbking/article/details/79787358

https://blog.csdn.net/yuyecsdn/article/details/90286084?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-1&spm=1001.2101.3001.4242

https://blog.csdn.net/qq_33409823/article/details/89205226

https://mp.weixin.qq.com/s?subscene=23&__biz=MzU4MDUyMDQyNQ==&mid=2247491533&idx=1&sn=91f5962332abc2364959b4a8fc7e5baf&chksm=fd54cf4bca23465d9c3a1a37841e903815784d22c6488635efe0d49e7f89f1e054883d7ff480&scene=7&key=778ec4de63c11d52c082faf806f7c648cea482e7018d5364943f18ccc745cb5b9a59f83944ed05b5ce649bcb4324510d220cf3c3498a405025743e4c41921b5b631e976b8c2ed11595314f7c7f140eedac42832bd9a2c05c79dd3c443f32a32c7b2478f728231ee6a2ee46ce005e08c62e7797a7663e78574c2117a6693d6df5&ascene=0&uin=MzMzMDAwOTE4Mw%3D%3D&devicetype=Windows+10+x64&version=63000039&lang=zh_CN&exportkey=AX2234kdxPwXzJ6nMtXX2yQ%3D&pass_ticket=yksO99DpucytELkVcbP4E9WpBKWDbbM9CG1Pq7jPP4LVkrsNKicFly9rnU8hSees&wx_header=0

6、结束

如果有写的不对的地方,还请指出,谢谢。

利用md5加密实现API接口的加签验签相关推荐

  1. SpringBoot 系列教程(八十五):Spring Boot使用MD5加盐验签Api接口之前后端分离架构设计

    加密算法参考: 浅谈常见的七种加密算法及实现 加密算法参考: 加密算法(DES,AES,RSA,MD5,SHA1,Base64)比较和项目应用 目的: 通过对API接口请求报文签名,后端进行验签处理, ...

  2. python实现md5加密_Python实现md5加密验证访问接口总结

    实现md5加密验证访问接口总结 作者:李祥权 [摘要] 在项目开发中,遇到用户端点登陆接口需要加密验证才可以登陆的情况,当我们需要访问对方的接口时,为了保证访问的安全性,使用python实现md5加密 ...

  3. 安全对外的接口--加签验签

    前言 我们在求职面试中,经常会被问到,如何设计一个安全对外的接口呢? 其实可以回答这一点,加签和验签,这将让你的接口更加有安全.接下来,本文将和大家一起来学习加签和验签.从理论到实战,加油哦~ 密码学 ...

  4. java rsa加密 加签_RSA加密解密与加签验签

    RSA公钥加密算法是1977年由罗纳德·李维斯特(Ron Rivest).阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的.1987年7月首次在美国公布 ...

  5. java支付宝rsa2签名,使用支付宝SDK进行RSAj加签验签以及加密解密

    /** * 支付宝 sdk 加签验签测试 * * @author 码农猿 */ public class SignDemo { /** * 公钥 */ private static final Str ...

  6. 聊一聊关于加解密、加签验签的那些事

    面对MD5.SHA.DES.AES.RSA等等这些名词你是否有很多问号?这些名词都是什么?还有什么公钥加密.私钥解密.私钥加签.公钥验签.这些都什么鬼?或许在你日常工作没有听说过这些名词,但是一旦你要 ...

  7. 速看: 加解密、加签验签,你想要的都在这了

    点击蓝色"java大数据修炼之道"关注我哟加个"星标",每晚21:00,一起学技术 来源: jianshu.com/p/5e9fe1fff6a3 作者: 不学无 ...

  8. 关于加解密、加签验签的那些事

    来源:r6d.cn/acJae 面对MD5.SHA.DES.AES.RSA等等这些名词你是否有很多问号?这些名词都是什么?还有什么公钥加密.私钥解密.私钥加签.公钥验签.这些都什么鬼?或许在你日常工作 ...

  9. 程序员必备基础:加签验签

    来源:捡田螺的小男孩 我们在求职面试中,经常会被问到,如何设计一个安全对外的接口呢? 其实可以回答这一点,加签和验签,这将让你的接口更加有安全.接下来,本文将和大家一起来学习加签和验签.从理论到实战, ...

最新文章

  1. BCH涨幅超过146%,势必跟BTC争夺王位?
  2. 安全测试基础 -- 概述【转载】
  3. Chrome DevTools — Network
  4. 对话MPEG创始人Leonardo Chiariglione: MPEG精神将在MPAI中延续
  5. spark 应用程序性能优化:12 个优化方法
  6. 迁移学习 nlp_NLP的发展-第3部分-使用ULMFit进行迁移学习
  7. 基于iSroll 5.0实现的上拉加载和下拉刷新插件
  8. mybatis思维导图,让mybatis不再难懂(二)
  9. 微信小程序进度条详解 progress 自定圆形进度条
  10. 每天五分钟linux(14)-head
  11. 看我如何绕过Cloudflare 的 SQL 注入过滤
  12. Java内存模型以及happens-before规则
  13. xhEditor入门基础
  14. 【pandas】结合泰坦尼克生还分析讲讲pandas常用基础操作
  15. echarts 实现图表缩放功能 dataZoom自带属性实现
  16. 抖音初期运营,如何让自己的抖音短视频账号快速涨粉:国仁楠哥
  17. 计算机教师职业幸福感,教师职业幸福感
  18. Springboot+oauth2.0实现微信登录(oauth2.0自定义授权模式)
  19. Python挑战游戏( PythonChallenge)闯关之路Level- 4
  20. linux为什么不需要磁盘碎片整理,Linux不用磁盘碎片整理原因分析.doc

热门文章

  1. HDU6599 (字符串哈希+回文自动机)
  2. SLAM开发环境搭建和配置
  3. vue canvas实现手写签字
  4. 调用文曲星里的英语词典中的语音(游戏发音处理)
  5. 现在流行的html软件,目前流行的前端框架APP设计制作软件介绍
  6. 【EEPROM AT24C02读正常,写不正常的解决办法】
  7. 在我认识到手机容量是有极限的之后……
  8. php 中如何设置单元格跨行,HTML表格中单元格跨行跨列
  9. C++ 主程序中打开子程序窗口隐藏,操作控件 遍历窗口控件句柄EnumChildWindows();获取其他程序进度条数据 操作选择框 按钮点击
  10. E0135 namespace “std“ has no member “back_inserter“