概述
该文档提供了对停车场开放接口请求的签名逻辑实现的详细说明。签名逻辑主要包括对请求参数的处理、排序以及使用 HMAC-SHA256 进行签名的步骤
签名生成步骤
准备数据:准备需要参与签名的数据,包括
AppId
、ParkKey
、TimeStamp
、Nonce
和Data
。处理Data:将
Data
进行ASCII码从小到大排序(升序)后转换为字符串(转换小写),再进行MD5加密(32模式,UTF-8编码)。具体步骤如下:- 如果
Data
存在数组或集合,将数组或集合转换为列表,对列表中的每个元素进行处理,然后使用&
符号连接起来。 - 如果
Data
是对象,则直接对该对象的所有字段进行排序,使用&
符号将字段和值连接起来。
- 如果
注意:Data为Null
时也要参与签名,转为Data=
,其他参数为Null
不参与签名
生成签名字符串:将
AppId
、Data
(已经处理过的)、ParkKey
、TimeStamp
和Nonce
按照这个顺序,使用&
符号连接起来,形成一个字符串。签名:将上一步生成的字符串转换为小写,然后使用HMAC-SHA256算法进行签名(签名结果转换成base64小写),密钥为
AppSecret
。
示例
假设我们有以下数据:
{
"ParkKey":"1702399313365243",
"AppId":"fkqju0n1",
"TimeStamp":"1704527859009",
"Nonce":"928885",
"Sign":"x1aeqmo3/hs9cbhlbnzburgskf1gvd/lil2t/iasiyo=",
"Data":[
{
"Index":1,
"Url":"http://aebhfqld.gt/tgmjt"
},
{
"Index":3,
"Url":"http://qtdupkzq.sd/dsicxsc"
},
{
"Url":"http://dfxkmte.tv/mpeop",
"Index":2
},
{
"Url":"http://hdphhisif.ni/uejufkem",
"Index":4
}
]
}
首先,我们需要处理Data
。我们将Data
的键值对按照ASCII码从小到大排序,得到:
Index=1&Url=http://aebhfqld.gt/tgmjt&Index=2&Url=http://dfxkmte.tv/mpeop&Index=3&Url=http://qtdupkzq.sd/dsicxsc&Index=4&Url=http://hdphhisif.ni/uejufkem
然后,我们对这个字符串转换成小写后进行MD5加密,假设得到的结果为md5Result
。
然后,我们生成签名字符串:
AppId=myAppId&Data=md5Result&ParkKey=myParkKey&TimeStamp=1632739200000&Nonce=myNonce
最后,我们将这个字符串转换为小写,然后使用HMAC-SHA256算法进行签名,密钥为myAppSecret
,得到的结果就是我们需要的签名。
注意事项
- 所有的键值对都需要按照ASCII码从小到大排序。
- HMAC-SHA256签名时,密钥应为
AppSecret
。 - 时间戳
TimeStamp
应为13位的Unix时间戳(毫秒级)。 Nonce
应为随机字符串,用于防止重复请求。- 其中
Data
为Null
时也要参与签名,其他字段为Null
不参与签名
相关代码示例
相关代码
HMAC-SHA256签名方法实现
/// <summary>
/// HMAC-SHA256 签名
/// </summary>
/// <param name="data">待签名数据</param>
/// <param name="secret">密钥</param>
/// <returns>签名后的数据, base64编码,转换成小写</returns>
public static string HmacSha256Sign(string data, string secret)
{
try
{
var encoding = new UTF8Encoding();
var keyByte = encoding.GetBytes(secret);
var messageBytes = encoding.GetBytes(data);
using var hmacsha256 = new System.Security.Cryptography.HMACSHA256(keyByte);
var hashmessage = hmacsha256.ComputeHash(messageBytes);
var result = Convert.ToBase64String(hashmessage);
return result.ToLower();
}
catch (Exception ex)
{
OpenApiLog.WriteLog("HMAC-SHA256 签名异常", ex);
return string.Empty;
}
}
Data对象排序成字符串方法实现
/// <summary>
/// 将Data进行ASCII码从小到大排序(升序)后转换为字符串
/// </summary>
/// <param name="data">请求数据</param>
/// <returns>转换后的字符串</returns>
private static string ConvertDataToString(object data)
{
// 如果数据为空,返回空字符串
if (data == null)
{
return string.Empty;
}
// 如果数据是数组或者集合
if (data.GetType().IsArray || data is IEnumerable<object>)
{
// 将数组转换为列表
var dataList = ((IEnumerable<object>)data).ToList();
// 使用递归的方式对列表中的每个元素进行处理
var dataDictList = dataList.Select(ConvertDataToString);
// 对集合使用稳定排序,保证相同的元素不会交换位置
dataDictList = dataDictList.OrderBy(p => p, StringComparer.Ordinal).ToList();
// 将每个元素转换为一个字符串,并使用&符号连接起来
return string.Join("&", dataDictList);
}
// 如果数据是对象
// 使用反射获取对象的属性值,并将其转换为一个键值对序列
var dataDict = data.GetType().GetProperties()
.Select(p =>
{
// 获取属性值
var value = p.GetValue(data);
// 如果属性值是数组,使用递归的方式进行处理
if (value != null && (value.GetType().IsArray || value is IEnumerable<object>))
{
value = ConvertDataToString(value);
}
// 返回键值对
return new KeyValuePair<string, object>(p.Name, value);
})
// 过滤掉值为null的键值对
.Where(p => p.Value != null)
// 对键值对序列使用稳定排序
.OrderBy(p => p.Key, StringComparer.Ordinal)
// 将键值对序列转换为字典
.ToDictionary(p => p.Key, p => p.Value);
// 将字典转换为一个字符串,并使用&符号连接起来
return string.Join("&", dataDict.Select(p => $"{p.Key}={p.Value}"));
}
作者:mry 创建时间:2024-10-24 09:01
更新时间:2024-10-24 11:29
更新时间:2024-10-24 11:29