请求签名

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("您访问太频繁了!");
            }
        }
    }
}
View Code

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;
        }
View Code

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();
        }
    }
}
View Code

 

posted @ 2022-03-17 21:03  江境纣州  阅读(76)  评论(0编辑  收藏  举报