数据签名说明
开放平台数据写接口,为了防止数据提交过程中被第三方篡改或者重复提交数据。在调用写接口时需要进行数据签名,服务器得到数据后,先进行验签,再调用业务。如果签名参数不符则业务调用失败。
第一步:先获取nonce值
在调用需要数据签名的接口之前,首先要调用nonce接口获取一个一次性验证码。
nonce验证码可同时存在多个,最长有效期为5分钟,且在使用一次后失效。
nonce验证码的长度并不确定,但是不会大于512个字符。
nonce接口调用说明
请求协议: HTTPS
请求方式: GET
请求地址: https://m-api.lejian.com/open-api/V2/nonce?accessToken=ACCESS_TOKEN
参数 | 是否必须 | 说明 |
---|---|---|
accessToken | 是 | 当前可用的accessToken |
正常情况下接口返回如下数据
{
"success":"T",
"data":{
"result":"0HpsLui7o8xHj_V_uoCgJZNUwilp9R_7"
},
"msg":"success"
}
返回数据说明
字段 | 说明 |
---|---|
result | 本次请求产生的nonce验证码 |
错误示例
{
"success":"F",
"errCode":"101101",
"msg":"Invaild Access Token"
}
表示使用的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或空字符串,那么该参数不参与计算,(参考举例说明),需注意两种特殊情况:
(4)最后拼接得到请求参数字符串contextStr = key+value+key+value。0值、false值也需要参与计算
空list、空array、空map,空对象,如果参数值携带也需要参与计算,如果您使用的语言很难区分[]和{},建议这类空值直接在参数中去除
举例说明如下:
参数名 | 参数值 | 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
package com.mytijian.openapi.util;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.util.StringUtils;
import java.security.MessageDigest;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author JiaChaojie
* @date 2022/11/7
* @email chaojie.jia@lejian.com
*/
public class SignUtils {
/**
* 数据签名处理方法
* @param nonce
* @param context
* @param signKey
* @return
*/
public static String sign(String nonce, String context, String signKey) {
if (nonce==null || context==null || signKey==null) {
return null;
}
//请注意这里转换格式之后如果是复杂数据格式会被保留原Json格式
Map<String,Object> contextMap = (Map<String, Object>) JSON.parse(context);
Map<String, Object> sortContextMap = contextMap.entrySet().stream()
//字母先后顺序排序
.sorted(Map.Entry.comparingByKey())
//过滤空值
.filter(x -> !StringUtils.isEmpty(x.getValue()))
//转换成新的map
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
(oleValue, newValue) -> oleValue, LinkedHashMap::new));
//初始化签名 nonce在首位
StringBuffer stringBuffer = new StringBuffer(nonce);
//按照KeyValue追加拼接参数
sortContextMap.forEach((k,v)->{
stringBuffer.append(k).append(v);
});
//最后追加signKey
stringBuffer.append(signKey);
//MD5之后得到最终的sign值
String sign = stringMD5(stringBuffer.toString());
return sign;
}
public static String stringMD5(String param) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
byte[] inputByteArray = param.getBytes("utf-8");
messageDigest.update(inputByteArray);
byte[] resultByteArray = messageDigest.digest();
return byteArrayToHex(resultByteArray);
} catch (Exception e) {
return null;
}
}
public static String byteArrayToHex(byte[] byteArray) {
char[] hexDigits = {'0','1','2','3','4','5','6','7','8','9', 'A','B','C','D','E','F' };
char[] resultCharArray =new char[byteArray.length * 2];
int index = 0;
for (byte b : byteArray) {
resultCharArray[index++] = hexDigits[b>>> 4 & 0xf];
resultCharArray[index++] = hexDigits[b& 0xf];
}
return new String(resultCharArray);
}
public static void main(String[] args) {
//先通过接口获取nonce值,这里直接赋值模拟
String nonce = "dMpGpvuLxlvhGcJhY_aViQpA9tpA6Iib";
//请求参数 map格式
Map<String,Object> param = new HashMap<>();
param.put("examDate","2022-11-15");
param.put("mealId","17444");
param.put("hospitalId","4876");
param.put("mobile","13335150001");
param.put("examAccountName","王老师");
param.put("idCard","110101199403075495");
param.put("idType","1");
param.put("married","0");
param.put("gender","0");
param.put("age","28");
param.put("bookBirthYear","1994");
param.put("bookDateBirth","0307");
param.put("companyId",4303726);
List<Integer> mealIds = Arrays.asList(1,2,3);
param.put("mealIds", mealIds);
HashMap<String,Object> examinee = new HashMap<>();
examinee.put("name","张三");
examinee.put("idCard","411403199004257532");
HashMap<String,Object> examineeExtendInfo = new HashMap<>();
examineeExtendInfo.put("a","a");
examinee.put("examineeExtendInfo",examineeExtendInfo);
param.put("examinee", examinee);
//空map也参与
param.put("emptyMap", new HashMap<>());
//signKey
String signKey = "f9fb17b361a141ddba0d0038ce7d4775";
//计算签名
String signStr = sign(nonce, JSONObject.toJSONString(param), signKey);
System.out.println(signStr);
}
}
示例:签名生成工具类-php
<?php
//待补充
?>
示例:签名生成工具类-python
#待补充
第三步:使用nonp接口验证签名是否正确
// 请注意 使用的是sign而不是signKey
String requestUrl = “https://m-api.lejian.com/open-api/nonp?accessToken="+accessToken+"&nonce="+nonce+"&sign="+sign;
// 发送post请求(地址,内容)
doHttpPostRequest(requestUrl, content);
如果nonce有效且sign验证成功,则根据具体业务返回数据。
常见错误如下:
```json
{
"success":"F",
"errCode":"101102",
"msg":"require 'sign' and 'nonce' param"
}
url中缺少sign或者nonce参数
{
"success":"F",
"errCode":"101103",
"msg":"sign error"
}
sign值错误,可能引起的原因是字符串组装顺序不正确、或者secret与服务器端不一致造成的
{
"success":"F",
"errCode":"101104",
"msg":" Invaild Nonce String"
}
无效的nonce, nonce获取之后只能使用一次,且只在5分钟内有效。