項目中常用的API接口簽名驗證方法:
1. 給app分配對應的key、secret
2. Sign簽名,調用API 時需要對請求參數進行簽名驗證,簽名方式如下:
a. 按照請求參數名稱將所有請求參數按照字母先后順序排序得到:keyvaluekeyvalue...keyvalue 字符串如:將arong=1,mrong=2,crong=3 排序為:arong=1, crong=3,mrong=2 然后將參數名和參數值進行拼接得到參數字符串:arong1crong3mrong2;
b. 將secret加在參數字符串的頭部后進行MD5加密 ,加密后的字符串需大寫。即得到簽名Sign;
大致處理過程
// 用戶驗證、判斷key是否存在,同時查詢出對應的secret用於驗證;
....
// 驗證簽名,根據算法將參數進行簽名得到sign與參數中的sign進行對比;
....
// 插敘數據獲取列表
如下圖

app調用:http://api.test.com/getproducts?key=app_key&sign=BCC7C71CF93F9CDBDB88671B701D8A35&參數1=value1&參數2=value2.......
注:secret 僅作加密使用, 為了保證數據安全請不要在請求參數中使用。
請求的唯一性: 為了防止別人重復使用請求參數問題,我們需要保證請求的唯一性,就是對應請求只能使用一次,這樣就算別人拿走了請求的完整鏈接也是無效的。
唯一性的實現:在如上的請求參數中,我們加入時間戳 :timestamp(yyyyMMddHHmmss),同樣,時間戳作為請求參數之一,也加入sign算法中進行加密。
下面提供2個簽名驗證的方法:時間戳注意加入及驗證的處理
1、OpenId簽名及驗證
/// <summary>
/// 生成Code
/// </summary>
/// <param name="openid">openid</param>
/// <param name="key">向誰跳傳誰規定的key</param>
/// <returns></returns>
public static string MakeCode(string openid, string key)
{
DateTime time = DateTime.Now;
string data = time.ToString("dd") + "_" + openid + "_" + time.ToString("yyyy") + "_" + key + "_" + time.ToString("MM");
MD5 md5 = new MD5CryptoServiceProvider();
byte[] result = Encoding.Default.GetBytes(data);
byte[] output = md5.ComputeHash(result);
data = BitConverter.ToString(output).Replace("-", "").ToLower();
SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
byte[] bytes = sha1.ComputeHash(Encoding.ASCII.GetBytes(data));//"596d42faf5710b35c7ea0f0a9600ee31" F69D39E1CA07FC23C1CE62F549E9D5B9780
//轉16進制 清除前面0
StringBuilder strB = new StringBuilder();
for (int i = 0; i < bytes.Length; i++)
{
string strX2 = bytes[i].ToString("X2");
if (strX2.Substring(0, 1) == "0")
{
strX2 = strX2.Substring(1, 1);
}
strB.Append(strX2);
}
return strB.ToString();
}
/// <summary>
/// Code驗證
/// </summary>
/// <param name="openid">openid</param>
/// <param name="code">待驗證的數據</param>
/// <param name="key">自己系統規定的key</param>
/// <returns></returns>
public static bool ValidateCode(string openid, string code, string key)
{
string signedData = MakeCode(openid, key);
return code.Equals(signedData, StringComparison.OrdinalIgnoreCase);
}
2、請求參數簽名及驗證
/// <summary>
/// 給請求簽名
/// </summary>
/// <param name="parameters">所有字符型的請求參數</param>
/// <param name="secret">簽名密鑰</param>
/// <param name="qhs">是否前后都加密進行簽名</param>
/// <returns></returns>
public string SignRequest(IDictionary<string, string> parameters, string secret, bool qhs)
{
// 第一步:把字典按Key的字母順序排序
IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parameters);
IEnumerator<KeyValuePair<string, string>> dem = sortedParams.GetEnumerator();
// 第二步:把所有參數名和參數值串在一起
StringBuilder query = new StringBuilder(secret);
while (dem.MoveNext())
{
string key = dem.Current.Key;
string value = dem.Current.Value;
if (!string.IsNullOrWhiteSpace(key))//!string.IsNullOrWhiteSpace(value) 空值也加入計算??
{
query.Append(key).Append(value);
}
}
if (qhs)
{
query.Append(secret);
}
// 第三部:使用md5運算
MD5 md5 = MD5.Create();
byte[] bytes = md5.ComputeHash(Encoding.UTF8.GetBytes(query.ToString()));
// 第四部:把二進制轉為大寫的十六進制
StringBuilder result = new StringBuilder();
for (int i = 0; i < bytes.Length; i++)
{
result.Append(bytes[i].ToString("X2"));
}
return result.ToString();
}
/// <summary>
/// 驗證簽名
/// </summary>
/// <returns></returns>
public bool ValidateSign(string secret)
{
string method = HttpContext.Current.Request.HttpMethod;
System.Collections.Specialized.NameValueCollection form = HttpContext.Current.Request.QueryString;
switch (method)
{
case "POST":
form = HttpContext.Current.Request.Form;
break;
case "GET":
form = HttpContext.Current.Request.QueryString;
break;
default:
return false;
}
IDictionary<string, string> parameters = new Dictionary<string, string>();
string sign = string.Empty;
for (int i = 0; i < form.Count; i++)
{
string key = form.Keys[i];
if (string.Equals(key,"sign",StringComparison.OrdinalIgnoreCase))
{
sign = form["sign"];
continue;
}
parameters.Add(key,form[key]);
}
return string.Equals(SignRequest(parameters, secret, false), sign,StringComparison.OrdinalIgnoreCase);
}
3、對用戶信息進行簽名(通用權限管理系統最新的安全校驗方法) ,實現了鏈接只可以使用一次
/// <summary>
/// 對登錄的用戶進行數字簽名
/// </summary>
/// <param name="userLogOnResult">登錄信息</param>
/// <returns>進行過數字簽名的用戶登錄信息</returns>
public static bool VerifySignature(BaseUserInfo userInfo)
{
bool result = false;
if (userInfo != null && !string.IsNullOrEmpty(userInfo.Signature))
{
if (string.IsNullOrEmpty(userInfo.Code))
{
userInfo.Code = string.Empty;
}
if (string.IsNullOrEmpty(userInfo.CompanyCode))
{
userInfo.CompanyCode = string.Empty;
}
if (string.IsNullOrEmpty(userInfo.CompanyId))
{
userInfo.CompanyId = string.Empty;
}
if (string.IsNullOrEmpty(userInfo.CompanyName))
{
userInfo.CompanyName = string.Empty;
}
if (string.IsNullOrEmpty(userInfo.DepartmentCode))
{
userInfo.DepartmentCode = string.Empty;
}
if (string.IsNullOrEmpty(userInfo.DepartmentId))
{
userInfo.DepartmentId = string.Empty;
}
if (string.IsNullOrEmpty(userInfo.DepartmentName))
{
userInfo.DepartmentName = string.Empty;
}
if (string.IsNullOrEmpty(userInfo.Id))
{
userInfo.Id = string.Empty;
}
if (string.IsNullOrEmpty(userInfo.NickName))
{
userInfo.NickName = string.Empty;
}
if (string.IsNullOrEmpty(userInfo.OpenId))
{
userInfo.OpenId = string.Empty;
}
if (string.IsNullOrEmpty(userInfo.RealName))
{
userInfo.RealName = string.Empty;
}
if (string.IsNullOrEmpty(userInfo.UserName))
{
userInfo.UserName = string.Empty;
}
// 需要簽名的內容部分
string dataToSign = userInfo.Code + "_"
+ userInfo.CompanyCode + "_"
+ userInfo.CompanyId + "_"
+ userInfo.CompanyName + "_"
+ userInfo.DepartmentCode + "_"
+ userInfo.DepartmentId + "_"
+ userInfo.DepartmentName + "_"
+ userInfo.Id + "_"
+ userInfo.IdentityAuthentication.ToString() + "_"
+ userInfo.IsAdministrator.ToString() + "_"
+ userInfo.NickName + "_"
+ userInfo.OpenId + "_"
+ userInfo.RealName + "_"
+ userInfo.UserName;
result = userInfo.Signature == DotNet.Utilities.SecretUtil.md5(dataToSign);
}
return result;
}
在用戶登錄成功以后要對返回的用戶信息進行一次簽名
UserLogOnResult result = new UserLogOnResult();
result = userManager.LogOnByNickName(userName, password, openId, createOpenId, systemCode, ipAddress, macAddress, computerName, checkUserPassword, validateUserOnly, checkMacAddress, sourceType, targetApplication, targetIp);
result.UserInfo = ServiceUtil.CreateSignature(result.UserInfo);
code的生成
/// <summary>
/// 獲取登錄操作的驗證碼
/// code作為換取access_token的票據,每次用戶授權帶上的code將不一樣,code只能使用一次,5分鍾未被使用自動過期。
/// </summary>
/// <param name="userInfo">用戶信息</param>
/// <returns>操作碼</returns>
public static BaseResult GetAuthorizationCode(BaseUserInfo userInfo)
{
BaseResult result = new BaseResult();
if (ServiceUtil.VerifySignature(userInfo))
{
// 產生一個授權碼
string authorizationCode = Guid.NewGuid().ToString("N");
// 設置緩存服務器,消費一次,5分鍾過期。
using (var redisClient = PooledRedisHelper.GetTokenClient())
{
// 2016-03-03 吉日嘎拉 讓緩存早點兒失效
DateTime expiresAt = DateTime.Now.AddMinutes(5);
string key = "code:" + authorizationCode;
redisClient.Set(key, userInfo.OpenId, expiresAt);
}
result.ResultValue = authorizationCode;
result.Status = true;
result.StatusCode = Status.OK.ToString();
result.StatusMessage = Status.OK.ToDescription();
result.CreateSignature(userInfo);
}
return result;
}
code的驗證
/// <summary>
/// 驗證授權碼
/// 用掉一次后,一定要消費掉,確保只能用一次。
/// </summary>
/// <param name="userInfo">當前用戶信息</param>
/// <param name="code">授權碼</param>
/// <param name="openId">用戶唯一識別碼</param>
/// <returns>驗證成功</returns>
public static bool VerifyAuthorizationCode(BaseUserInfo userInfo, string code, out string openId)
{
bool result = false;
openId = string.Empty;
if (userInfo != null && !ServiceUtil.VerifySignature(userInfo))
{
return result;
}
using (var redisClient = PooledRedisHelper.GetTokenClient())
{
// 2016-03-03 吉日嘎拉 讓緩存早點兒失效
string key = "code:" + code;
openId = redisClient.Get<string>(key);
if (!string.IsNullOrEmpty(openId))
{
result = true;
if (userInfo != null && !string.IsNullOrEmpty(userInfo.OpenId))
{
result = userInfo.OpenId.Equals(openId);
}
}
redisClient.Remove(key);
}
return result;
}
返回數據結果類
/// <summary>
/// BaseResult JsonResult<T>
///
/// 修改記錄
///
/// 2016.05.12 版本:2.1 JiRiGaLa 增加 Signature 數字簽名。
/// 2016.01.07 版本:2.0 JiRiGaLa 增加 RecordCount。
/// 2015.11.16 版本:1.1 SongBiao 增加JsonResult<T> 泛型 可以帶數據返回。
/// 2015.09.16 版本:1.1 JiRiGaLa Result 修改為 Status。
/// 2015.09.15 版本:1.0 JiRiGaLa 添加返回標准定義。
///
/// <author>
/// <name>JiRiGaLa</name>
/// <date>2016.05.12</date>
/// </author>
/// </summary>
[Serializable]
public class BaseResult
{
/// <summary>
/// 操作是否成功
/// 2015-09-16 吉日嘎拉 按宋彪建議進行修正
/// </summary>
public bool Status = false;
/// <summary>
/// 返回值
/// </summary>
public string ResultValue = "";
/// <summary>
/// 返回狀態代碼
/// </summary>
public string StatusCode = "UnknownError";
/// <summary>
/// 返回消息內容
/// </summary>
public string StatusMessage = "未知錯誤";
/// <summary>
/// 查詢分頁數據時返回記錄條數用
/// </summary>
public int RecordCount = 0;
/// <summary>
/// 數字簽名(防止篡改)
/// </summary>
public string Signature = string.Empty;
/// <summary>
/// 對登錄的用戶進行數字簽名
/// </summary>
/// <param name="userInfo">登錄信息</param>
/// <returns>進行過數字簽名的用戶登錄信息</returns>
public string CreateSignature(BaseUserInfo userInfo)
{
if (userInfo != null)
{
if (!string.IsNullOrEmpty(userInfo.Signature))
{
// 需要簽名的內容部分
string dataToSign = userInfo.Signature + "_"
+ ResultValue + "_"
+ Status.ToString() + "_"
+ StatusCode.ToString() + "_"
+ BaseSystemInfo.SecurityKey + "_";
// 進行簽名
Signature = DotNet.Utilities.SecretUtil.md5(dataToSign);
}
}
return Signature;
}
/// <summary>
/// 對登錄的用戶進行數字簽名
/// </summary>
/// <param name="userInfo">登錄信息</param>
/// <returns>進行過數字簽名的用戶登錄信息</returns>
public bool VerifySignature(BaseUserInfo userInfo)
{
bool result = false;
if (userInfo != null)
{
if (!string.IsNullOrEmpty(userInfo.Signature))
{
// 需要簽名的內容部分
string dataToSign = userInfo.Signature + "_"
+ ResultValue + "_"
+ Status.ToString() + "_"
+ StatusCode.ToString() + "_"
+ BaseSystemInfo.SecurityKey + "_";
// 進行簽名
result = Signature == DotNet.Utilities.SecretUtil.md5(dataToSign);
}
}
return result;
}
}
/// <summary>
/// Json格式帶返回數據
/// </summary>
/// <typeparam name="T"></typeparam>
[Serializable]
public class JsonResult<T> : BaseResult
{
public T Data { get; set; }
}
覺得可以,幫忙頂一下,謝謝。

