数据签名说明

开放平台数据写接口,为了防止数据提交过程中被第三方篡改或者重复提交数据。在调用写接口时需要进行数据签名,服务器得到数据后,先进行验签,再调用业务。如果签名参数不符则业务调用失败。

第一步:先获取nonce值

在调用需要数据签名的接口之前,首先要调用nonce接口获取一个一次性验证码。

nonce验证码可同时存在多个,最长有效期为5分钟,且在使用一次后失效。
nonce验证码的长度并不确定,但是不会大于512个字符。

nonce接口调用说明

请求协议: HTTPS
请求方式: GET
请求地址: https://m-api.lejian.com/open-api/V2/nonce?accessToken=ACCESS_TOKEN

参数 是否必须 说明
accessToken 当前可用的accessToken

正常情况下接口返回如下数据

  1. {
  2. "success":"T",
  3. "data":{
  4. "result":"0HpsLui7o8xHj_V_uoCgJZNUwilp9R_7"
  5. },
  6. "msg":"success"
  7. }

返回数据说明

字段 说明
result 本次请求产生的nonce验证码

错误示例

  1. {
  2. "success":"F",
  3. "errCode":"101101",
  4. "msg":"Invaild Access Token"
  5. }

表示使用的accessToken错误或过期


第二步:计算签名值

签名示例 (模拟调用业务接口)

现在我们假设需要调用业务接口 /nonp
https请求方式:post
https请求地址:https://m-api.lejian.com/open-api/V2/nonp?accessToken=ACCESS_TOKEN&nonce=NONCE&sign=SIGN (举例使用,如无此接口调用权限,请联系对接人员开通)
app signKey 值为 eccdcff429b342399582d81029652ae9 (业务对接人员处提前获取)
接口请求内容为: {“test”:”context use sign test”}

验签算法:

若您使用java语言开发,直接使用下方示例工具类进行签名,若使用其他语言,请仔细阅读签名算法说明

  • 第一步
    (1)先将参数按照key的字母先后顺序排序,然后按照key+value+key+value的方式拼接,此处排序参数只包括请求报文request_body,接口链接上的query参数不参与
    (2)参数值如果是基本类型数据转换成字符串,如果是复杂结构值(比如List、object)需要整个转换成json字符串后参与计算,需使用标准转义后json格式:{\”aa\”:\”aa\”,\”bb\”:\”bb\”},尽量使用标准json工具转换;(参考举例说明)
    (3)如果某个参数是null或空字符串,那么该参数不参与计算,(参考举例说明),需注意两种特殊情况:
    1. 0值、false值也需要参与计算
    2. list、空array、空map,空对象,如果参数值携带也需要参与计算,如果您使用的语言很难区分[]和{},建议这类空值直接在参数中去除
    (4)最后拼接得到请求参数字符串contextStr = key+value+key+value。
    举例说明如下
参数名 参数值 key+value格式 备注
mealId 1001 mealId1001 普通参数直接转换为字符串
pkgIds [1,2,3] pkgIds[1,2,3] json格式,请使用json标准库工具转换,勿手拼
examinee {“name”:”张三”} examinee{\”name\”:\”张三”} json格式,请使用json标准库工具转换,勿手拼
testInfo {“test”:”context use sign test”} testInfo{\”test\”:\”context use sign test\”} json格式,请使用json标准库工具转换,勿手拼
sendMsg false sendMsgfalse 所有布尔值都参与,请注意全部小写字母true/false
does 0 does0 0参与
hospital {} hospital{} 空map参与计算
items [] items[] 空 list/array 参与计算
orderPrice null 不参与计算
memo “” 不参与计算
  • 第二步
    拼接上nonce和signKey字符串,得到种子字符串seedStr = nonce + contextStr + signKey
  • 第三步
    对种子进行MD5转码(32位),并将字母全部转大写,得到最终的签名值 sigin = md5(seedStr).upcase
示例:签名生成工具类-java
  1. package com.mytijian.openapi.util;
  2. import com.alibaba.fastjson.JSON;
  3. import com.alibaba.fastjson.JSONObject;
  4. import org.springframework.util.StringUtils;
  5. import java.security.MessageDigest;
  6. import java.util.*;
  7. import java.util.stream.Collectors;
  8. /**
  9. * @author JiaChaojie
  10. * @date 2022/11/7
  11. * @email chaojie.jia@lejian.com
  12. */
  13. public class SignUtils {
  14. /**
  15. * 数据签名处理方法
  16. * @param nonce
  17. * @param context
  18. * @param signKey
  19. * @return
  20. */
  21. public static String sign(String nonce, String context, String signKey) {
  22. if (nonce==null || context==null || signKey==null) {
  23. return null;
  24. }
  25. //请注意这里转换格式之后如果是复杂数据格式会被保留原Json格式
  26. Map<String,Object> contextMap = (Map<String, Object>) JSON.parse(context);
  27. Map<String, Object> sortContextMap = contextMap.entrySet().stream()
  28. //字母先后顺序排序
  29. .sorted(Map.Entry.comparingByKey())
  30. //过滤空值
  31. .filter(x -> !StringUtils.isEmpty(x.getValue()))
  32. //转换成新的map
  33. .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
  34. (oleValue, newValue) -> oleValue, LinkedHashMap::new));
  35. //初始化签名 nonce在首位
  36. StringBuffer stringBuffer = new StringBuffer(nonce);
  37. //按照KeyValue追加拼接参数
  38. sortContextMap.forEach((k,v)->{
  39. stringBuffer.append(k).append(v);
  40. });
  41. //最后追加signKey
  42. stringBuffer.append(signKey);
  43. //MD5之后得到最终的sign值
  44. String sign = stringMD5(stringBuffer.toString());
  45. return sign;
  46. }
  47. public static String stringMD5(String param) {
  48. try {
  49. MessageDigest messageDigest = MessageDigest.getInstance("MD5");
  50. byte[] inputByteArray = param.getBytes("utf-8");
  51. messageDigest.update(inputByteArray);
  52. byte[] resultByteArray = messageDigest.digest();
  53. return byteArrayToHex(resultByteArray);
  54. } catch (Exception e) {
  55. return null;
  56. }
  57. }
  58. public static String byteArrayToHex(byte[] byteArray) {
  59. char[] hexDigits = {'0','1','2','3','4','5','6','7','8','9', 'A','B','C','D','E','F' };
  60. char[] resultCharArray =new char[byteArray.length * 2];
  61. int index = 0;
  62. for (byte b : byteArray) {
  63. resultCharArray[index++] = hexDigits[b>>> 4 & 0xf];
  64. resultCharArray[index++] = hexDigits[b& 0xf];
  65. }
  66. return new String(resultCharArray);
  67. }
  68. public static void main(String[] args) {
  69. //先通过接口获取nonce值,这里直接赋值模拟
  70. String nonce = "dMpGpvuLxlvhGcJhY_aViQpA9tpA6Iib";
  71. //请求参数 map格式
  72. Map<String,Object> param = new HashMap<>();
  73. param.put("examDate","2022-11-15");
  74. param.put("mealId","17444");
  75. param.put("hospitalId","4876");
  76. param.put("mobile","13335150001");
  77. param.put("examAccountName","王老师");
  78. param.put("idCard","110101199403075495");
  79. param.put("idType","1");
  80. param.put("married","0");
  81. param.put("gender","0");
  82. param.put("age","28");
  83. param.put("bookBirthYear","1994");
  84. param.put("bookDateBirth","0307");
  85. param.put("companyId",4303726);
  86. List<Integer> mealIds = Arrays.asList(1,2,3);
  87. param.put("mealIds", mealIds);
  88. HashMap<String,Object> examinee = new HashMap<>();
  89. examinee.put("name","张三");
  90. examinee.put("idCard","411403199004257532");
  91. HashMap<String,Object> examineeExtendInfo = new HashMap<>();
  92. examineeExtendInfo.put("a","a");
  93. examinee.put("examineeExtendInfo",examineeExtendInfo);
  94. param.put("examinee", examinee);
  95. //空map也参与
  96. param.put("emptyMap", new HashMap<>());
  97. //signKey
  98. String signKey = "f9fb17b361a141ddba0d0038ce7d4775";
  99. //计算签名
  100. String signStr = sign(nonce, JSONObject.toJSONString(param), signKey);
  101. System.out.println(signStr);
  102. }
  103. }
示例:签名生成工具类-php
  1. <?php
  2. //待补充
  3. ?>
示例:签名生成工具类-python
  1. #待补充

第三步:使用nonp接口验证签名是否正确

// 请注意 使用的是sign而不是signKey
String requestUrl = “https://m-api.lejian.com/open-api/nonp?accessToken="+accessToken+"&nonce="+nonce+"&sign="+sign;

// 发送post请求(地址,内容)
doHttpPostRequest(requestUrl, content);

  1. 如果nonce有效且sign验证成功,则根据具体业务返回数据。
  2. 常见错误如下:
  3. ```json
  4. {
  5. "success":"F",
  6. "errCode":"101102",
  7. "msg":"require 'sign' and 'nonce' param"
  8. }

url中缺少sign或者nonce参数

  1. {
  2. "success":"F",
  3. "errCode":"101103",
  4. "msg":"sign error"
  5. }

sign值错误,可能引起的原因是字符串组装顺序不正确、或者secret与服务器端不一致造成的

  1. {
  2. "success":"F",
  3. "errCode":"101104",
  4. "msg":" Invaild Nonce String"
  5. }

无效的nonce, nonce获取之后只能使用一次,且只在5分钟内有效。

文档更新时间: 2023-11-20 18:08