1概念

开放接口

开放接口是指不需要登录凭证就允许被第三方系统调用的接口。为了防止开放接口被恶意调用,开放接口一般都需要验签才能被调用。提供开放接口的系统下面统一简称为"原系统"。

验签

验签是指第三方系统在调用接口之前,需要按照原系统的规则根据所有请求参数生成一个签名(字符串),在调用接口时携带该签名。原系统会验证签名的有效性,只有签名验证有效才能正常调用接口,否则请求会被驳回。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

2接口验签调用流程

1. 约定签名算法

第三方系统作为调用方,需要与原系统协商约定签名算法(下面以SHA256withRSA签名算法为例)。同时约定一个名称(callerID),以便在原系统中来唯一标识调用方系统。

2. 颁发非对称密钥对

签名算法约定后之后,原系统会为每一个调用方系统专门生成一个专属的非对称密钥对(RSA密钥对)。私钥颁发给调用方系统,公钥由原系统持有。

注意,调用方系统需要保管好私钥(存到调用方系统的后端)。因为对于原系统而言,调用方系统是消息的发送方,其持有的私钥唯一标识了它的身份是原系统受信任的调用方。调用方系统的私钥一旦泄露,调用方对原系统毫无信任可言。

3. 生成请求参数签名

签名算法约定后之后,生成签名的原理如下(活动图)。

为了确保生成签名的处理细节与原系统的验签逻辑是匹配的,原系统一般都提供jar包或者代码片段给调用方来生成签名,否则可能会因为一些处理细节不一致导致生成的签名是无效的。

4. 请求携带签名调用

路径参数中放入约定好的callerID,请求头中放入调用方自己生成的签名

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud
  • 视频教程:https://doc.iocoder.cn/video/

3代码设计

1. 签名配置类

相关的自定义yml配置如下。RSA的公钥和私钥可以使用hutool的SecureUtil工具类来生成,注意公钥和私钥是base64编码后的字符串

定义一个配置类来存储上述相关的自定义yml配置

importcn.hutool.core.util.StrUtil;importorg.springframework.lang.Nullable;importorg.springframework.util.ObjectUtils;importjava.util.Arrays;importjava.util.Map;importjava.util.stream.Collectors;publicclassCommonUtils{/***提取所有的请求参数,按照固定规则拼接成一个字符串**@parambodypost请求的请求体*@paramparamMap路径参数(QueryString)。形如:name=zhangsan&age=18&label=A&label=B*@paramuriTemplateVarNap路径变量(PathVariable)。形如:/{name}/{age}*@return所有的请求参数按照固定规则拼接成的一个字符串*/publicstaticStringextractRequestParams(@NullableStringbody,@NullableMap<String,String[]>paramMap,@NullableMap<String,String>uriTemplateVarNap){//body:{userID:"xxx"}//路径参数//name=zhangsan&age=18&label=A&label=B//=>["name=zhangsan","age=18","label=A,B"]//=>name=zhangsan&age=18&label=A,BStringparamStr=null;if(!ObjectUtils.isEmpty(paramMap)){paramStr=paramMap.entrySet().stream().sorted(Map.Entry.comparingByKey()).map(entry->{//拷贝一份按字典序升序排序String[]sortedValue=Arrays.stream(entry.getValue()).sorted().toArray(String[]::new);returnentry.getKey()+"="+joinStr(",",sortedValue);}).collect(Collectors.joining("&"));}//路径变量///{name}/{age}=>/zhangsan/18=>zhangsan,18StringuriVarStr=null;if(!ObjectUtils.isEmpty(uriTemplateVarNap)){uriVarStr=joinStr(",",uriTemplateVarNap.values().stream().sorted().toArray(String[]::new));}//{userID:"xxx"}#name=zhangsan&age=18&label=A,B#zhangsan,18returnjoinStr("#",body,paramStr,uriVarStr);}/***使用指定分隔符,拼接字符串**@paramdelimiter分隔符*@paramstrs需要拼接的多个字符串,可以为null*@return拼接后的新字符串*/publicstaticStringjoinStr(Stringdelimiter,@NullableString...strs){if(ObjectUtils.isEmpty(strs)){returnStrUtil.EMPTY;}StringBuildersbd=newStringBuilder();for(inti=0;i<strs.length;i++){if(ObjectUtils.isEmpty(strs[i])){continue;}sbd.append(strs[i].trim());if(!ObjectUtils.isEmpty(sbd)&&i<strs.length-1&&!ObjectUtils.isEmpty(strs[i+1])){sbd.append(delimiter);}}returnsbd.toString();}}
本文代码

https://github.com/passerbyYSQ/DemoRepository


本篇文章来源于微信公众号: 芋道源码



微信扫描下方的二维码阅读本文

此作者没有提供个人介绍
最后更新于 2023-11-13