软件协议文档
终端设备协议文档
功能&协议池(所有功能协议)
MQTT协议文档
HTTP协议文档
微光TLV协议文档
微光卡&码协议
功能模块&UI文档
人脸机
VF105
门禁标品MQTT协议文档(DejaOS版本)
Access control standard MQTT protocol document
VF系列HTTP协议接口文档V1.0.28(Vbar版本)
VF系列MQTT协议文档V1.37.0(Vbar版本)
VF系列-协议模式下通讯协议文档V1.0.4(Vbar版本)
VF203
门禁标品MQTT协议文档(DejaOS版本)
Access control standard MQTT protocol document
VF系列HTTP协议接口文档V1.0.28(Vbar版本)
VF系列-协议模式下通讯协议文档V1.0.4(Vbar版本)
VF系列-MQTT协议文档V1.37.2(Vbar版本)
VF 系列 HTTP 协议接口文档-V1.37.1(Vbar版本)
VF106
门禁标品MQTT协议文档(Android版本)
Access control standard MQTT protocol document
VF114
门禁标品MQTT协议文档(DejaOS版本)
Access control standard MQTT protocol document
VF系列HTTP协议接口文档V1.0.28(Vbar版本)
VF系列-协议模式下通讯协议文档V1.0.4(Vbar版本)
VF205
门禁标品MQTT协议文档(DejaOS版本)
VF系列HTTP协议接口文档V1.0.28(Vbar版本)
VF系列-协议模式下通讯协议文档V1.0.4(Vbar版本)
读头
EE200
读头标品TLV通讯协议
Read header standard TLV communication protocol
读头标品TLV通讯协议V3.10(Vbar版本)
TX200
读头标品TLV通讯协议
Read header standard TLV communication protocol
QT960
读头标品TLV通讯协议
Read header standard TLV communication protocol
QT660
读头标品TLV通讯协议
Read header standard TLV communication protocol
Q340
读头标品TLV通讯协议
Read header standard TLV communication protocol
M300
读头标品TLV通讯协议
Read header standard TLV communication protocol
读头标品TLV通讯协议V3.10(Vbar版本)
JL7000
读头标品TLV通讯协议
Read header standard TLV communication protocol
Q350
读头标品TLV通讯协议
Read header standard TLV communication protocol
MU86
读头标品TLV通讯协议
Read header standard TLV communication protocol
读头标品TLV通讯协议V3.10(Vbar版本)
MET
读头标品TLV通讯协议
Read header standard TLV communication protocol
扫码器TCP/HTT通讯协议V1.0(Vbar版本)
读头标品TLV通讯协议V3.10(Vbar版本)
M350
读头标品TLV通讯协议
Read header standard TLV communication protocol
读头标品TLV通讯协议V3.10(Vbar版本)
扫码器TCP/HTT通讯协议V1.0(Vbar版本)
DW200
读头标品TLV通讯协议(DejaOS版本)
Read header standard TLV communication protocol
读头标品HTTP&TCP协议文档(DejaOS版本)
读头标品HTTP&TCP协议文档(Vbar版本)
读头标品TLV通讯协议V3.10(Vbar版)
读头标品RS485一拖多协议V0.7(Vbar版本)
M340
读头标品TLV通讯协议
Read header standard TLV communication protocol
CR90
刷卡模块通信协议v3.0
CR90指令文档
微光指令:0x60 蓝牙设备控制
1.7/2.x扫码器配置字段说明文档
门禁
DW200
门禁标品MQTT协议文档(DejaOS版本)
Access control standard MQTT protocol document
门禁扫码器MQTT协议文档(Vbar版本)
MU86
门禁标品MQTT协议文档
Access control standard MQTT protocol document
门禁20180820 MQTT协议文档V1.0.2(Vbar版)
Q350
门禁标品MQTT协议文档
Access control standard MQTT protocol document
门禁20180820 MQTT协议文档V1.0.2(Vbar版)
MET
门禁标品MQTT协议文档
Access control standard MQTT protocol document
门禁20180820 MQTT协议文档V1.0.2(Vbar版)
M350
门禁标品MQTT协议文档
Access control standard MQTT protocol document
门禁20180820 MQTT协议V1.0.1(Vbar版本)
门禁20180820 MQTT协议文档V1.0.2(Vbar版本)
MP86
门禁20180820 MQTT协议文档V1.0.2(Vbar版本)
控制板
CC104
控制板标品MQTT协议文档
Control board standard MQTT protocol documentation
CC101
控制板标品MQTT协议文档
Control board standard MQTT protocol documentation
CC101标品20211101MQTT协议V3.6(Vbar版本)
平台服务协议文档
网关服务接口定义
门禁应用接口定义
工具文档
多弦产品API签名安全规则
海外锁
app和后台的mqtt协议
文档
-
+
首页
多弦产品API签名安全规则
API 对外暴露需要有安全机制保证只有经过授权的用户才能访问,通常使用加密和签名来保证基本的安全。 加密通常使用 HTTPS 协议确保,这里不详述。签名机制是确保未授权的请求在 API 提供方能验证出来并拒绝执行相应的服务。 # <font color ='#40A977'>**一.**</font> 基本原理 <img src="/media/202502/WechatIMG4369_20250214161736347106.jpg" width="800" height="auto" /> 如上图,未签名的请求很简单,就是请求方调用提供方提供的 API 接口,调用的时候会带上一些请求的数据,提供方收到后然后返回相应的数据。 签名的方式,是指请求方请求 API 接口时,除了基础的数据外还需要附带一个签名,签名也是数据,是通过约定的规则生成的数据;提供方收到请求的数据和签名后,利用规则验证签名的合法性,如果验证通过,则正常返回相应的数据,验证失败,则直接返回错误信息。 签名安全基本原理都一致,但是实现的规则没有标准,每个 API 提供厂商都有细微的差别,多弦的签名规则参考了微信、移动等大厂标准,和他们保持基本一致。 # <font color ='#40A977'>**二.**</font> 基本规则 ## 1. 申请密钥组 首先多弦 API 使用的用户需要和多弦约定一组密钥,可以是私下发送,也可以通过多弦的平台线上生成,包括 AppKey 和 AppSecret 。其中**AppSecret 一定要确保不要泄漏出去**,否则安全就得不到保证。 ## 2. header 添加项 所有的 API 请求的 http报文的 header 里需要包含三个新的值 | 参数标识| 参数名称| 参数类型| 位置| 是否必须| 描述 | |------|------|------|------|------|---------| |<font color ='#0092db'>1.</font>AppKey|开发者key| String| header| 是|和 API 使用方约定的 AppKey 值,这个值无需保密,任何人都可以获取。 |<font color ='#0092db'>2.</font>Sign|开发者密钥| String |header| 是| 签名字符串,详细见:签名规则 |<font color ='#0092db'>3.</font>Timestamp|时间戳 |String| header| 是|请求的时间戳 ,格式是 YYYYMMDDHH24MISS (GMT时间,和国内时间会相差8小时) ## 3. 签名规则(Sign值生成的规则) 1) **参与签名参数** - url 请求中的 params (如:http://ip/sys?a=bbb&c=稍等&b=e发e,其中'a=bbb&c=稍等&b=e发e') - 请求体中的 body (如:body={"a":2311,"b":2444,"c":"sdfasdfasdfasdf为空离开sd","d":"2022-03-24 11:23:44"}) 2) **排序** - params 入参根据 key 进行字典序进行排序,排序后 params="a=bbb&b=e发e&c=稍等" - body 不参与排序,不替换空格和换行 3) **拼接** 规则:<font color ='#0092db'>排序后的params</font>+<font color ='##A52A2A'>body</font>+<font color ='#6A5ACD'>AppSecret</font>+<font color ='#FFA500'>timestamp</font> 示例:"<font color ='#0092db'>a=bbb&b=e发e&c=稍等</font><font color ='##A52A2A'>{"a":2311,"b":2444,"c":"sdfasdfasdfasdf为空离开sd","d":"2022-03-24 11:23:44"}</font><font color ='#6A5ACD'>AppSecret</font><font color ='#FFA500'>20220714073654</font>" ``` a=bbb&b=e发e&c=稍等{"a":2311,"b":2444,"c":"sdfasdfasdfasdf为空离开sd","d":"2022-03-24 11:23:44"}AppSecret120220714073654 ``` 4) **MD5** 对上一步骤拼接完的字符串做 MD5 操作,并做一下处理最后转换成16进制字符串,以上的拼接字符串经过处理后得到字符串 ``` 3336613838313731336233346366303866386635306561303964623438663861 ``` # <font color ='#40A977'>**三.**</font> Java 工具 多弦提供多种开发语言的工具和示例,方便 API 使用者快速对接上签名规则。开发者可以直接拷贝代码使用。 ``` java import d1.duoxian.webapi.configuration.ContentCachingRequestWrapper; import org.springframework.util.StringUtils; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.text.SimpleDateFormat; import java.util.*; public class HmacSignUtil { public static final String ENCODE = "UTF-8"; private static final String MD5_ALGORITHM = "MD5"; public static final String DATE_FORMAT_PATTERN = "yyyyMMddHHmmss"; //获取当前的GMT时间,和国内时间偏差8小时,这个时间必须准确,允许5分钟的偏差 public static String getCurTimestamp() { long timestamp = System.currentTimeMillis(); Date date = new Date(timestamp); SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT_PATTERN, Locale.US); dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); return dateFormat.format(date); } public static String signMD5(String reqStr) throws Exception { System.out.println("拼接字符串:"+reqStr); char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; byte[] btInput = reqStr.getBytes(); MessageDigest mdInst = MessageDigest.getInstance(MD5_ALGORITHM); mdInst.update(btInput); byte[] md = mdInst.digest(); int j = md.length; char[] str = new char[j * 2]; int k = 0; for (byte byte0 : md) { str[k++] = hexDigits[byte0 >>> 4 & 0xf]; str[k++] = hexDigits[byte0 & 0xf]; } return bytesToHexString(new String(str).getBytes(ENCODE)).toUpperCase(Locale.US); } public static String bytesToHexString(byte[] b) { StringBuilder sb = new StringBuilder(); for (byte value : b) { String hex = Integer.toHexString(value & 0xFF); if (hex.length() == 1) { hex = '0' + hex; } sb.append(hex); } return sb.toString(); } //对http url 参数的排序,格式是xxx=yyy&ssss=zzz public static String order(String params) throws Exception { if(!StringUtils.hasLength((params))){ return ""; } Map<String, String> map = new HashMap<>(); try { String[] kvs = params.split("&"); for (String kv : kvs) { String[] kav = kv.split("="); map.put(kav[0], kav[1]); } } catch (Exception e) { throw new Exception("URL 参数不规范"); } List<String> tmp = new ArrayList<>(); List<Map.Entry<String, String>> infoIds = new ArrayList<>(map.entrySet()); infoIds.sort(Map.Entry.comparingByKey()); for (Map.Entry<String, String> item : infoIds) { tmp.add(item.getKey() + "=" + item.getValue()); } return String.join("&", tmp); } } ``` 测试的代码 ``` java private static void test() { RestTemplate restTemplate = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); String paramStr = "a=bbb&c=稍等&b=e发e"; JSONObject object = new JSONObject(); object.put("a",2311); object.put("b",2444L); object.put("c","sdfasdfasdfasdf为空离开sd"); object.put("d","2022-03-24 11:23:44"); String bodyStr = object.toString(); try { paramStr = HmacSignUtil.order(paramStr); String timestamp = HmacSignUtil.getCurTimestamp(); String appSecret = "appSecret1"; String reqStr = paramStr + bodyStr + appSecret + timestamp; String sign = HmacSignUtil.signMD5(reqStr); System.out.println("签名是:" + sign); headers.add("AppKey", "appkey1"); headers.add("Sign", sign); headers.add("Timestamp", timestamp); try { ResponseEntity<String> response = restTemplate.postForEntity(rootUrl + "test3?a=bbb&c=稍等&b=e发e", new HttpEntity<JSONObject>(object, headers), String.class); System.out.println(response.getStatusCode()); System.out.println(response.getBody()); } catch (Exception ex) { System.out.println("返回:" + ex.getLocalizedMessage()); } } catch (Exception e) { e.printStackTrace(); } } ``` # <font color ='#40A977'>**四.**</font> C# 工具 除 java 外,还提供了 C# 相应的代码和工具类。 ``` C# public static String DATE_FORMAT_PATTERN = "yyyyMMddHHmmss"; private String getCurTimestamp() { return DateTime.UtcNow.ToString(DATE_FORMAT_PATTERN); } char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; public String signMD5(String str) { byte[] bytePassword = Encoding.UTF8.GetBytes(str); using (MD5 md5 = MD5.Create()) { byte[] bytes = md5.ComputeHash(bytePassword); StringBuilder result = new StringBuilder(bytes.Length * 2); int j = bytes.Length; char[] cstr = new char[j * 2]; int k = 0; foreach (byte byte0 in bytes) { cstr[k++] = hexDigits[byte0 >> 4 & 0xf]; cstr[k++] = hexDigits[byte0 & 0xf]; } return bytesToHexString(Encoding.UTF8.GetBytes(new string(cstr))).ToUpper(); } } public string bytesToHexString(byte[] b) { if (b == null) return null; StringBuilder sb = new StringBuilder(b.Length * 2); for (int i = 0; i < b.Length; i++) sb.AppendFormat("{0:x2}", b[i]); return sb.ToString(); } ``` # <font color ='#40A977'>**五.**</font> Python 工具 提供了 Python 相应的代码和工具类。 ``` python import requests import time import json import hashlib import urllib.parse # 对http url 参数的排序,格式是xxx=yyy&ssss=zzz def order(params): if params is None or len(params) == 0: return "" map = {} kvs = params.split("&") for kv in kvs: kav = kv.split("=") map[kav[0]]=kav[1] order_keys = sorted(map.keys()) order_values = [] for key in order_keys: order_values.append(key + "=" + map[key]) return "&".join(order_values) # //获取当前的GMT时间,和国内时间偏差8小时,这个时间必须准确,允许5分钟的偏差 def getGMTTimestamp(): return time.strftime("%Y%m%d%H%M%S", time.gmtime(time.time())) def signMD5(reqStr): hl = hashlib.md5() hl.update(reqStr.encode(encoding="utf-8")) md5_bytes = hl.digest() # hexdigest() hexDigits = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102] values = [] for b in md5_bytes: values.append(hexDigits[b >> 4 & 0xF]) values.append(hexDigits[b & 0xF]) return bytearray(bytes(values)).hex() def test(): url = "http://localhost:8086/service/testhmac/test3?a=bbb&c=稍等&b=e发e" header = {"Content-Type": "application/json; charset=UTF-8"} paramStr = "a=bbb&c=稍等&b=e发e" body={} body["a"]= 2311 body["b"] = 2444 body["c"] ="sdfasdfasdfasdf为空离开sd" body["d"] = "2022-03-24 11:23:44" bodyStr = json.dumps(body) timestamp = getGMTTimestamp() appSecret = "appSecret1" paramStr = order(paramStr) reqStr = paramStr + bodyStr + appSecret + timestamp sign = signMD5(reqStr) print("签名是:" + sign) header["Timestamp"] = timestamp header["AppKey"] = "appkey1" header["Sign"] = sign r =requests.post(url, headers=header, data=bodyStr) print(r.json()) test() ``` # <font color ='#40A977'>**六.**</font> PHP 工具 提供了 php 相应的代码。 ``` php <?php function getGMTTimestamp() { date_default_timezone_set('UTC'); return date('YmdHis'); } function order($params) { if (empty($params)) { return ""; } $map = array(); try { $kvs = explode("&", $params); foreach ($kvs as $kv) { $kav = explode("=", $kv); $map[$kav[0]] = $kav[1]; } } catch (Exception $e) { throw new Exception("URL 参数不规范"); } $tmp = array(); $infoIds = array_map(function($key, $value) { return [$key => $value]; }, array_keys($map), $map); usort($infoIds, function($a, $b) { return strcmp(key($a), key($b)); }); foreach ($infoIds as $item) { $tmp[] = key($item) . "=" . current($item); } return implode("&", $tmp); } function signMD5($reqStr) { $hexDigits = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'); $mdInst = md5($reqStr,true); $md = $mdInst; $j = strlen($md); $str = ""; $k = 0; for ($i = 0; $i < $j; $i++) { $byte0 = ord($md[$i]); $str .= $hexDigits[$byte0 >> 4 & 0xf]; $str .= $hexDigits[$byte0 & 0xf]; } return strtoupper(strToHex(utf8_encode($str))); } function strToHex($str){ $hex=""; for($i=0;$i<strlen($str);$i++) $hex.=dechex(ord($str[$i])); $hex=strtoupper($hex); return $hex; } function test() { $paramStr = "a=bbb&c=稍等&b=e发e"; $body = array(); $body["a"] = 2311; $body["b"] = 2444; $body["c"] = "sdfasdfasdfasdf为空离开sd"; $body["d"] = "2022-03-24 11:23:44"; $bodyStr = json_encode($body, JSON_UNESCAPED_UNICODE); $timestamp = getGMTTimestamp(); $appSecret = "appSecret1"; $paramStr = order($paramStr); $reqStr = $paramStr . $bodyStr . $appSecret . $timestamp; echo "签名前字符串:".$reqStr; $sign = signMD5($reqStr); echo "签名是:" . $sign . "\n"; $header["Timestamp"] = $timestamp; $header["AppKey"] = "appkey1"; $header["Sign"] = $sign; // 初始化 cURL 句柄 $ch = curl_init(); // 设置要请求的 URL $url = "http://localhost:8086/service/testhmac/test3?a=bbb&c=稍等&b=e发e"; curl_setopt($ch, CURLOPT_URL, $url); // 设置请求方法为 POST curl_setopt($ch, CURLOPT_POST, true); // 设置请求头 $headers = array( "Content-Type: application/json; charset=UTF-8", "Timestamp: ".$timestamp, "AppKey: appkey1", "Sign: ".$sign ); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_POSTFIELDS, $bodyStr); // 设置 cURL 参数 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 发送请求并获取响应 $response = curl_exec($ch); // 关闭 cURL } test() ?> ``` # <font color ='#40A977'>**七.**</font> 在线测试工具 提供在线测试工具,方便 API 使用者在线测试和验证,包括签名相关的在线工具。 参考<a href="http://123.57.175.193:8087/api_test1.html" target="_blank">在线测试工具</a> # <font color ='#40A977'>**八.**</font> 签名相关错误提示 1. "签名校验失败,Header里没有包含AppKey" : API使用方需要添加对应的 AppKey 2. "签名校验失败,Header里没有包含Sign" : API使用方需要添加对应的 Sign 3. "签名校验失败,Header里没有包含Timestamp" : API使用方需要添加对应的 Timestamp 4. "签名校验失败,没有找到验证AppKey的实现类" : 请API使用方通知API提供方进行修复 5. "签名校验失败,没有找到当前指定的AppKey,请找管理员确认" : 请API使用方找API提供方提供 6. "签名校验失败,Timestamp格式错误,必须是yyyyMMddHHmmss" : 请API使用方确认时间格式 7. "签名校验失败,Timestamp超时错误" : 请API使用方确认时间是否使用的是当前的GMT时间,或是否时间已经和当前时间偏差太大 8. "签名校验失败,计算签名发生异常" : 请API使用方通知API提供方进行修复 9. "签名校验失败,签名值不匹配" : 请API使用方确认签名计算逻辑 [回到顶部](#top)
周小禹
2025年3月6日 15:11
89
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
Word文件
PDF文档
PDF文档(打印)
分享
链接
类型
密码
更新密码
有效期