接口签名规则及Java代码demo实现
接口签名规则及Java代码demo实现
签名规则
签名生成的通用步骤如下:
第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
特别注意以下重要规则:
◆ 参数名ASCII码从小到大排序(字典序);
◆ 如果参数的值为空不参与签名;
◆ 参数名区分大小写;
◆ 验证接口调用传送的sign参数不参与签名,将生成的签名与该sign值作校验。
第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。 注意:密钥的长度为32个字节。
1.导入jar
implementation("commons-beanutils:commons-beanutils:1.9.3")
2.MD5工具类
import java.security.MessageDigest; public class MD5 { private final static String[] hexDigits = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; /** * 转换字节数组为16进制字串 * @param b 字节数组 * @return 16进制字串 */ public static String byteArrayToHexString(byte[] b) { StringBuilder resultSb = new StringBuilder(); for (byte aB : b) { resultSb.append(byteToHexString(aB)); } return resultSb.toString(); } /** * 转换byte到16进制 * @param b 要转换的byte * @return 16进制格式 */ private static String byteToHexString(byte b) { int n = b; if (n < 0) { n = 256 + n; } int d1 = n / 16; int d2 = n % 16; return hexDigits[d1] + hexDigits[d2]; } /** * MD5编码 * @param origin 原始字符串 * @return 经过MD5加密之后的结果 */ public static String MD5Encode(String origin) { String resultString = null; try { resultString = origin; MessageDigest md = MessageDigest.getInstance("MD5"); resultString = byteArrayToHexString(md.digest(resultString.getBytes())); } catch (Exception e) { e.printStackTrace(); } return resultString; } }
3.实体类
import java.util.List; public class UploadReqVO { //手机号 private String mobile; //姓名 private String realname; //身份证号 private String idno; //字符数组 private List<String> testBase64Str; private String sign; public String getMobile() { return mobile; } public void setMobile(String mobile) { this.mobile = mobile; } public String getRealname() { return realname; } public void setRealname(String realname) { this.realname = realname; } public String getIdno() { return idno; } public void setIdno(String idno) { this.idno = idno; } public List<String> getTestBase64Str() { return testBase64Str; } public void setTestBase64Str(List<String> testBase64Str) { this.testBase64Str = testBase64Str; } public String getSign() { return sign; } public void setSign(String sign) { this.sign = sign; } }
4.签名类及测试类
import com.alibaba.fastjson.JSON; import org.apache.commons.beanutils.BeanUtils; import org.apache.tomcat.util.security.MD5Encoder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.StringUtils; import org.xml.sax.SAXException; import javax.xml.parsers.ParserConfigurationException; import java.io.FileWriter; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.*; public class Signature { private static Logger log = LoggerFactory.getLogger(Signature.class); /** * 对象入参,对象转MAP * @param object * @return * @throws Exception */ public static String getSignNew(Object object,String key) throws Exception { Map<String, String> map = BeanUtils.describe(object); return getSignNew(map,key); } public static String getSignNewArray(UploadReqVO object, String key) throws Exception { Map<String, String> map = BeanUtils.describe(object); //覆盖 map.put("testBase64Str", JSON.toJSONString(object.getTestBase64Str()).replace("\"","")); return getSignNew(map,key); } /** * 签名 * @param map * @return */ private static String getSignNew(Map<String,String> map,String key) throws Exception{ ArrayList<String> list = new ArrayList<String>(); for(Map.Entry<String,String> entry:map.entrySet()){ if(entry.getValue() != null && !StringUtils.isEmpty(entry.getValue().toString()) && !"null".equals(entry.getValue()) && !"class".equals(entry.getKey())){ //空字符串 entry.getValue()!=""){ list.add(entry.getKey() + "=" + entry.getValue() + "&"); } } int size = list.size(); String [] arrayToSort = list.toArray(new String[size]); // Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER); //忽略大小写 Arrays.sort(arrayToSort); StringBuilder sb = new StringBuilder(); for(int i = 0; i < size; i ++) { sb.append(arrayToSort[i]); } String result = sb.toString(); //过滤最后一个字符串& int lastIdx = result.lastIndexOf("&"); result = result.substring(0,lastIdx); // result += "key=" + key; //去掉key= 字符串 result += key; //key直接拼接在后面 try{ log.info("Sign Before MD5:"+ result); result = MD5.MD5Encode(result); log.info("Sign Result:" + result); }catch (Exception e) { e.printStackTrace(); } return result; } public static boolean checkIsSignValidFromResyponseStringObject(Object object,String key) throws Exception { Map<String, String> map = org.apache.commons.beanutils.BeanUtils.describe(object); return checkIsSignValidFromResponseString(map,key); } public static boolean checkIsSignValidFromResponseStringArray(UploadReqVO object, String key) throws Exception { Map<String, String> map = BeanUtils.describe(object); //覆盖 map.put("testBase64Str", JSON.toJSONString(object.getTestBase64Str()).replace("\"","")); return checkIsSignValidFromResponseString(map,key); } /** * object转换为map 验证签名 * @param map * @return * @throws ParserConfigurationException * @throws IOException * @throws SAXException * @throws IllegalAccessException */ private static boolean checkIsSignValidFromResponseString(Map<String,String> map,String key) throws Exception { String signFromAPIResponse = null; if(map.get("sign")!=null){ signFromAPIResponse = map.get("sign").toString(); } if(signFromAPIResponse=="" || signFromAPIResponse == null){ log.info("signFromAPIResponse报空"); return false; } //清掉返回数据对象里面的Sign数据(不能把这个数据也加进去进行签名),然后用签名算法进行签名 map.put("sign",""); map.put("class",""); //将API返回的数据根据用签名算法进行计算新的签名,用来跟API返回的签名进行比较 //重新签名 log.info("签名前的map="+map); String signForAPIResponse = Signature.getSignNew(map,key); log.info("签名后的字符串="+signForAPIResponse); if(!signForAPIResponse.equals(signFromAPIResponse)){ //签名验不过,表示这个API返回的数据有可能已经被篡改了 log.info("匹配不一致"); return false; } return true; } public static void main(String[] args) { String key = "testkey123testkey123testkey12345"; try { UploadReqVO uploadReqVo = new UploadReqVO(); List<String> pic1List = new ArrayList<String>(); String pic5 = Base64.getEncoder().encodeToString("测试字符串1".getBytes(StandardCharsets.UTF_8)); pic1List.add(pic5); String pic6 = Base64.getEncoder().encodeToString("测试字符串2".getBytes(StandardCharsets.UTF_8)); pic1List.add(pic6); uploadReqVo.setTestBase64Str(pic1List); uploadReqVo.setIdno("465601200810081204"); uploadReqVo.setRealname("测试员"); uploadReqVo.setMobile("19945558899"); uploadReqVo.setSign(""); //数组方法 String signStr3 = Signature.getSignNewArray(uploadReqVo,key); //非数组方法 // String signStr3 = Signature.getSignNew(uploadReqVo,key); System.out.println("(上传图片)签名字符串:" + signStr3); uploadReqVo.setSign(signStr3); System.out.println("(上传图片)参数json=" + JSON.toJSONString(uploadReqVo)); //验证签名 //数组方法 boolean flag3 = Signature.checkIsSignValidFromResponseStringArray(uploadReqVo,key); //非数组方法 // boolean flag3 = Signature.checkIsSignValidFromResyponseStringObject(uploadReqVo,key); System.out.println("(上传图片)验证签名是否一致="+flag3); } catch (Exception e) { e.printStackTrace(); } } }
打印输出日志对比: //数组方式 16:56:50.014 [main] INFO com.example.utils.Signature - Sign Before MD5:idno=465601200810081204&mobile=19945558899&realname=测试员&testBase64Str=[5rWL6K+V5a2X56ym5LiyMQ==,5rWL6K+V5a2X56ym5LiyMg==]testkey123testkey123testkey12345 16:56:50.061 [main] INFO com.example.utils.Signature - Sign Result:61867a7f32594eec1967fcddea8d96c3 (上传图片)签名字符串:61867a7f32594eec1967fcddea8d96c3 (上传图片)参数json={"idno":"465601200810081204","mobile":"19945558899","realname":"测试员","sign":"61867a7f32594eec1967fcddea8d96c3","testBase64Str":["5rWL6K+V5a2X56ym5LiyMQ==","5rWL6K+V5a2X56ym5LiyMg=="]} 16:56:50.089 [main] INFO com.example.utils.Signature - 签名前的map={testBase64Str=[5rWL6K+V5a2X56ym5LiyMQ==,5rWL6K+V5a2X56ym5LiyMg==], mobile=19945558899, sign=, class=, idno=465601200810081204, realname=测试员} 16:56:50.089 [main] INFO com.example.utils.Signature - Sign Before MD5:idno=465601200810081204&mobile=19945558899&realname=测试员&testBase64Str=[5rWL6K+V5a2X56ym5LiyMQ==,5rWL6K+V5a2X56ym5LiyMg==]testkey123testkey123testkey12345 16:56:50.089 [main] INFO com.example.utils.Signature - Sign Result:61867a7f32594eec1967fcddea8d96c3 16:56:50.089 [main] INFO com.example.utils.Signature - 签名后的字符串=61867a7f32594eec1967fcddea8d96c3 (上传图片)验证签名是否一致=true //非数组方法,弊端是:testBase64Str构建签名字符串的时候,默认取数组的第一个字符,而不是[]结构的全部数据。 16:59:15.692 [main] INFO com.example.utils.Signature - Sign Before MD5:idno=465601200810081204&mobile=19945558899&realname=测试员&testBase64Str=5rWL6K+V5a2X56ym5LiyMQ==testkey123testkey123testkey12345 16:59:15.697 [main] INFO com.example.utils.Signature - Sign Result:bfc9246143246f1852b4a29732aabcf6 (上传图片)签名字符串:bfc9246143246f1852b4a29732aabcf6 (上传图片)参数json={"idno":"465601200810081204","mobile":"19945558899","realname":"测试员","sign":"bfc9246143246f1852b4a29732aabcf6","testBase64Str":["5rWL6K+V5a2X56ym5LiyMQ==","5rWL6K+V5a2X56ym5LiyMg=="]} 16:59:15.775 [main] INFO com.example.utils.Signature - 签名前的map={testBase64Str=5rWL6K+V5a2X56ym5LiyMQ==, mobile=19945558899, sign=, class=, idno=465601200810081204, realname=测试员} 16:59:15.775 [main] INFO com.example.utils.Signature - Sign Before MD5:idno=465601200810081204&mobile=19945558899&realname=测试员&testBase64Str=5rWL6K+V5a2X56ym5LiyMQ==testkey123testkey123testkey12345 16:59:15.775 [main] INFO com.example.utils.Signature - Sign Result:bfc9246143246f1852b4a29732aabcf6 16:59:15.775 [main] INFO com.example.utils.Signature - 签名后的字符串=bfc9246143246f1852b4a29732aabcf6 (上传图片)验证签名是否一致=true
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)