項目中經常使用的API接口簽名驗證方法:redis
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;算法
大體處理過程api
// 用戶驗證、判斷key是否存在,同時查詢出對應的secret用於驗證;緩存
....安全
// 驗證簽名,根據算法將參數進行簽名獲得sign與參數中的sign進行對比;服務器
....app
// 插敘數據獲取列表ide
以下圖ui
app調用:http://api.test.com/getproducts?key=app_key&sign=BCC7C71CF93F9CDBDB88671B701D8A35&參數1=value1&參數2=value2.......
注:secret 僅做加密使用, 爲了保證數據安全請不要在請求參數中使用。
請求的惟一性: 爲了防止別人重複使用請求參數問題,咱們須要保證請求的惟一性,就是對應請求只能使用一次,這樣就算別人拿走了請求的完整連接也是無效的。
惟一性的實現:在如上的請求參數中,咱們加入時間戳 :timestamp(yyyyMMddHHmmss),一樣,時間戳做爲請求參數之一,也加入sign算法中進行加密。加密
下面提供2個簽名驗證的方法:時間戳注意加入及驗證的處理
/// <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); }
/// <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); }
/// <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; } }
通常企業內部應用相互之間接口調用是不須要進行參數的簽名驗證的,大部分狀況下是提供給第三方時纔會須要。若是您有更好的接口簽名驗證方法,歡迎分享~~~