请求签名
1、https可以保证信道安全,但是不能保证数据源安全,所以可以对请求头签名的方式。
2、签名方法

using HRS.Lib.Comm.Exceptions; using HRS.Lib.Comm.Memory; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Configuration; using System.Linq; using System.Net.Http.Headers; using System.Security.Cryptography; using System.Text; using System.Web; namespace HRS.Web.Helper { /// <summary> /// 请求头签名 /// </summary> public static class SignHelper { private static string[] _AllKeys = ConfigurationManager.AppSettings.AllKeys; private static ConcurrentDictionary<string, string> _cDic = new ConcurrentDictionary<string, string>(); /// <summary> /// 校验 /// </summary> /// <param name="headers"></param> public static bool Check(HttpRequestHeaders headers) { // 1、校验请求头 var dicKeys = ValidateHeader(headers); // 2、校验签名 var signStr = Sign(dicKeys); var signature = headers.GetValues("signature").First(); if (signStr != signature) { throw new ShowInfoException("【调试阶段提示】签名失败:应该是{0},而不是{1}", signStr, signature); } // 3、防重放攻击 ReplayAttack(dicKeys["timestamp"], dicKeys["xReqNonce"]); return true; } /// <summary> /// 校验请求头 /// </summary> /// <param name="headers"></param> /// <returns></returns> private static Dictionary<string, string> ValidateHeader(HttpRequestHeaders headers) { Dictionary<String, String> dicKeys = new Dictionary<String, String>(); if (!headers.Contains("appKey")) throw new ShowInfoException("请求头缺省appKey"); if (!headers.Contains("timestamp")) throw new ShowInfoException("请求头缺省timestamp"); if (!headers.Contains("version")) throw new ShowInfoException("请求头缺省version"); if (!headers.Contains("xReqNonce")) throw new ShowInfoException("请求头缺省xReqNonce"); if (!headers.Contains("signature")) throw new ShowInfoException("请求头缺省signature"); var appKey = headers.GetValues("appKey").First(); var timestamp = headers.GetValues("timestamp").First(); var version = headers.GetValues("version").First(); var xReqNonce = headers.GetValues("xReqNonce").First(); var signature = headers.GetValues("signature").First(); if (string.IsNullOrEmpty(appKey)) throw new ShowInfoException("请求头缺省appKey"); if (string.IsNullOrEmpty(timestamp)) throw new ShowInfoException("请求头缺省timestamp"); if (string.IsNullOrEmpty(version)) throw new ShowInfoException("请求头缺省version"); if (string.IsNullOrEmpty(xReqNonce)) throw new ShowInfoException("请求头缺省xReqNonce"); if (string.IsNullOrEmpty(signature)) throw new ShowInfoException("请求头缺省signature"); // 校验 ValidateAppKey(appKey); dicKeys.Add("appKey", appKey); dicKeys.Add("appSecret", GetAppSecret(appKey)); dicKeys.Add("timestamp", headers.GetValues("timestamp").First()); dicKeys.Add("version", headers.GetValues("version").First()); dicKeys.Add("xReqNonce", headers.GetValues("xReqNonce").First()); return dicKeys; } /// <summary> /// 获取appSecret /// </summary> /// <param name="appKey"></param> /// <returns></returns> private static void ValidateAppKey(string appKey) { if (!_AllKeys.Any(p => p == appKey)) { throw new ShowInfoException("请求头appKey不存在"); } } /// <summary> /// 签名验证 /// </summary> /// <param name="dicKeys"></param> /// <param name="signature"></param> private static string Sign(Dictionary<string, string> dicKeys) { // 1、 将以上key=value对的value进行合并,生成一下字符串builder StringBuilder builder = new StringBuilder(); foreach (var item in dicKeys) { builder.Append(item.Value); } // 2.、将生成的builder进行Url编码 string urlBuilder = HttpUtility.UrlEncode(builder.ToString(), Encoding.UTF8); Console.WriteLine(urlBuilder); // 3、签名 var encoding = new System.Text.UTF8Encoding(); byte[] keyByte = encoding.GetBytes(dicKeys["appSecret"]); byte[] messageBytes = encoding.GetBytes(urlBuilder); using (var hmacsha256 = new HMACSHA256(keyByte)) { byte[] hashmessage = hmacsha256.ComputeHash(messageBytes); return Convert.ToBase64String(hashmessage); } } /// <summary> /// 获取appSecret /// </summary> /// <param name="appKey"></param> /// <returns></returns> private static string GetAppSecret(string appKey) { if (!_cDic.ContainsKey(appKey)) { _cDic[appKey] = ConfigurationManager.AppSettings[appKey]; } return _cDic[appKey]; } private static object obj = new object(); /// <summary> /// 防重放 /// </summary> /// <param name="timestamp"></param> /// <param name="xReqNonce"></param> private static void ReplayAttack(string timestamp, string xReqNonce) { var stamp = CacheMemory<Object>.GetCache(timestamp + xReqNonce); if (stamp == null) { CacheMemory<Object>.SetCache(timestamp + xReqNonce, obj, DateTime.Now.AddSeconds(60)); // 60秒 } else { throw new ShowInfoException("您访问太频繁了!"); } } } }
3、服务端(缺省数据库实现)

// POST api/UserLogin/ApiToken [HttpPost] [AllowAnonymous] [ActionName("ApiToken")] public ApiTokenResponeModel ApiToken(ApiTokenRequestModel model) { var respone = new ApiTokenResponeModel(); try { // 请求头校验 respone.issuccess = SignHelper.Check(Request.Headers); // 登录校验 var appKey = Request.Headers.GetValues("appKey").First(); respone.registered = Registered(model, appKey); // 生成jwt if(respone.registered) respone.access_token = TokenHelper.GenerateToken(model.businesslicenseNo, 5); } catch (ShowInfoException e) { respone.msg = e.Message; } catch (Exception ex) { respone.msg = ex.Message; BusinessEngine.Logger.Error(ex); } return respone; } // POST api/UserLogin/Register [HttpPost] [AllowAnonymous] [ActionName("Register")] public RegisterResponeModel Register(RegisterRequestModel model) { var respone = new RegisterResponeModel(); try { // 请求头校验 SignHelper.Check(Request.Headers); // 注册 var appKey = Request.Headers.GetValues("appKey").First(); Register(model, appKey); // 生成jwt respone.access_token = TokenHelper.GenerateToken(model.businesslicenseNo, 5); respone.issuccess = true; } catch (ShowInfoException e) { respone.msg = e.Message; } catch (Exception ex) { respone.msg = ex.Message; BusinessEngine.Logger.Error(ex); } return respone; }
4、客户端

using System; using System.Collections.Generic; using System.Text; using System.Security.Cryptography; using System.Net; using System.Web; using System.IO; namespace ConsoleApplication2 { class Program { static void Main(string[] args) { string appKey = "1111111111111111111"; string appSecret = "2222222222"; // 1970至今的时差, //string timestamp = ((DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 1000).ToString(); string timestamp = "16473983036990"; string version = "1.0.0"; // string xReqNonce = Guid.NewGuid().ToString().Replace("-", ""); string xReqNonce = "24213r42fd"; // 错的 // 1、按Key字典顺序 Dictionary<String, String> dicKeys = new Dictionary<String, String>(); dicKeys.Add("appKey", appKey); dicKeys.Add("appSecret", appSecret); dicKeys.Add("timestamp", timestamp); dicKeys.Add("version", version); dicKeys.Add("xReqNonce", xReqNonce); // 2、 将以上key=value对的value进行合并,生成一下字符串builder StringBuilder builder = new StringBuilder(); foreach (var item in dicKeys) { builder.Append(item.Value); } Console.WriteLine(builder.ToString()); // 3.、将生成的builder进行Url编码 string urlBuilder = HttpUtility.UrlEncode(builder.ToString(), Encoding.UTF8); Console.WriteLine(urlBuilder); // 4、签名 var encoding = new System.Text.UTF8Encoding(); byte[] keyByte = encoding.GetBytes(appSecret); byte[] messageBytes = encoding.GetBytes(urlBuilder); using (var hmacsha256 = new HMACSHA256(keyByte)) { byte[] hashmessage = hmacsha256.ComputeHash(messageBytes); string signStr = Convert.ToBase64String(hashmessage); dicKeys.Add("signature", signStr); Console.WriteLine(signStr); } var url = "http://localhost:8888/api/UserLogin/ApiToken"; dicKeys.RemoveKey("appSecret"); // 移除Header中的appSecret //var json = JsonConvert.SerializeObject(new {BusinesslicenseNo="913303821455606740"}); var json = "{\"BusinesslicenseNo\":\"913303821455606740\"}"; try { var responeJson = GetResponseString(url, dicKeys, json); } catch (Exception e) { // TODO } Console.ReadKey(); } /// <summary> /// 获取请求的数据 /// </summary> /// <param name="strUrl">请求地址</param> /// <param name="dicHeaders">请求头</param> /// <param name="jsonParameters">json参数</param> /// <returns>返回:请求成功响应信息,失败返回null</returns> public static string GetResponseString(string strUrl, Dictionary<string, string> dicHeaders, string jsonParameters) { HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(strUrl); HttpWebResponse webResponse = PostRequest(webRequest, dicHeaders, jsonParameters); if (webResponse != null && webResponse.StatusCode == HttpStatusCode.OK) { using (Stream newStream = webResponse.GetResponseStream()) { if (newStream != null) { using (StreamReader reader = new StreamReader(newStream)) { string result = reader.ReadToEnd(); return result; } } } } return null; } /// <summary> /// post 请求指定地址返回响应数据 /// </summary> /// <param name="webRequest">请求</param> /// <param name="dicHeaders">请求头</param> /// <param name="requestBody">请求体</param> /// <returns>返回:响应信息</returns> private static HttpWebResponse PostRequest(HttpWebRequest webRequest, Dictionary<string, string> dicHeaders, string requestBody) { byte[] byteArray = Encoding.UTF8.GetBytes(requestBody); webRequest.ContentType = "application/json"; webRequest.ContentLength = byteArray.Length; webRequest.Method = "POST"; /// 请求头 foreach (var key in dicHeaders.Keys) { webRequest.Headers.Add(key, dicHeaders[key]); } // 将参数写入流 using (Stream newStream = webRequest.GetRequestStream()) { newStream.Write(byteArray, 0, byteArray.Length); newStream.Close(); } // 接收返回信息 return (HttpWebResponse)webRequest.GetResponse(); } } }