C# Nancy框架開發 WebApi 二:接口安全簽名認證


上一章記錄了創建一個Nancy框架的WebApi接口,這一章就在這個接口Demo上繼續添加簽名安全認證,保證接口的數據請求安全

  

一:創建一個MD5加密類,按照自己的加密方式來寫

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Security;
namespace Security
{
    public class MD5
    {
     // 加密
public static string Encrypt(string str) { string result = string.Empty; string cl = DateTime.Now.Month + str + DateTime.Now.Day; var md5 = new System.Security.Cryptography.MD5CryptoServiceProvider(); byte[] data = md5.ComputeHash(Encoding.Default.GetBytes(cl)); data.Reverse(); for (int i = 0; i < data.Length; i++) { result += data[i].ToString("X"); } return result; } } }

 

二:創建接口授權密鑰 (這里用配置類來代替,實際可以配置在數據庫中)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace NancyWebApiDemo.Security
{
    public class LicenceConfig
    {
        private static Dictionary<string, string> Licences = new Dictionary<string, string>();

        public LicenceConfig()
        {
            if (Licences.Count == 0)
            {
                Licences.Add("%%8795456$#@1198456451)(##@", "userOne"); //用戶1的Api授權密鑰
                Licences.Add("$984351321515##&*135131133#", "userTwo");  //用戶2的Api授權密鑰
            }
        }

        //獲取擁有密鑰系統用戶
        public string GetLicencesUser(string Key)
        {
            return Licences[Key];
        }

        //檢索密鑰是否存在
        public bool CheckExistLicence(string Key)
        {
            return Licences.ContainsKey(Key);
        }
    }
}

 

創建一個緩存操作類。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Caching;

namespace NancyWebApiDemo.Common
{
    public class CacheHelper
    {
        /// <summary>  
        /// 獲取數據緩存  
        /// </summary>  
        /// <param name="cacheKey"></param>  
        public static object GetCache(string cacheKey)
        {
            var objCache = HttpRuntime.Cache.Get(cacheKey);
            return objCache;
        }
        /// <summary>  
        /// 設置數據緩存  
        /// </summary>  
        public static void SetCache(string cacheKey, object objObject)
        {
            var objCache = HttpRuntime.Cache;
            objCache.Insert(cacheKey, objObject);
        }
        /// <summary>  
        /// 設置數據緩存  
        /// </summary>  
        public static void SetCache(string cacheKey, object objObject, int timeout = 7200)
        {
            try
            {
                if (objObject == null) return;
                var objCache = HttpRuntime.Cache;
                //過期時間  
                objCache.Insert(cacheKey, objObject, null, DateTime.Now.AddSeconds(timeout), TimeSpan.Zero, CacheItemPriority.High, null);
            }
            catch (Exception)
            {
                //throw;  
            }
        }
        /// <summary>  
        /// 移除指定數據緩存  
        /// </summary>  
        public static void RemoveCache(string cacheKey)
        {
            var cache = HttpRuntime.Cache;
            cache.Remove(cacheKey);
        }
        /// <summary>  
        /// 移除全部緩存  
        /// </summary>  
        public static void RemoveAllCache()
        {
            var cache = HttpRuntime.Cache;
            var cacheEnum = cache.GetEnumerator();
            while (cacheEnum.MoveNext())
            {
                cache.Remove(cacheEnum.Key.ToString());
            }
        }
    }
}

 

 

三:在ApiModule.cs 創建簽名獲取接口

//獲取Api簽名   
            Post["/getSign"] = p =>
            {
                CommResponse<object> response = new CommResponse<object>();
                response.Code = CodeConfig.CodeFailed;
                try
                {
                    string key = Request.Query["key"]; //獲取
                    string data = Request.Query["data"];//請求的json數據
                    string type = Request.Query["type"]; //請求動作
                    bool flag = new Security.LicenceConfig().CheckExistLicence(key);
                    if (flag)
                    {
                        //創建簽名
                        switch (type)
                        {
                            case "Query":
                                response.Message = "請求成功";
                                response.Code = CodeConfig.CodeSuccess;
                                response.Data = Security.MD5.Encrypt(type + key + data);
                                break;
                            case "Write":
                                response.Message = "請求成功";
                                response.Code = CodeConfig.CodeSuccess;
                                response.Data = Security.MD5.Encrypt(type + key + data);
                                break;
                            default:
                                response.Message = "接口操作類型錯誤";
                                break;
                        }
                        //獲取簽名成功
                        if (response.Code == CodeConfig.CodeSuccess)
                        {
                            //設置一個簽名過期時間:120秒
                            CacheHelper.SetCache(response.Data as string, response.Data, 120);
                        }
                    }
                    else
                    {
                        response.Message = "接口授權密鑰不存在";
                    }
                }
                catch (Exception ex)
                {
                    response.Message = ex.Message;
                }
                return Response.AsText(JsonHelper.ObjectConvertJson(response), "application/json");
            };

 

接下來把項目運行起來 用Postman 工具測試下簽名接口

 

 

傳入正確的密鑰

 

在這里已經拿到了簽名,自己的程序應該馬上跟着請求數據接口 查詢或者寫入數據,因為我們設置了簽名的120秒有效期。

 

四:在ApiModule.cs 中創建一個簽名認證方法

     /// <summary>
        /// 驗證簽名
        /// </summary>
        /// <param name="type">操作類型</param>
        /// <param name="data">請求的源數據</param>
        /// <param name="sign">簽名</param>
        /// <returns></returns>
        public CommResponse<object> VerificationSign(string type, string key, string data, string sign)
        {
            CommResponse<object> response = new CommResponse<object>();
            response.Code = CodeConfig.CodeFailed;

            //計算簽名
            string old = Security.MD5.Encrypt(type + key + data);
            if (old.Equals(sign))
            {
                //繼續判斷簽名是否過期
                object _data = CacheHelper.GetCache(sign);
                if (_data == null)
                {
                    response.Message = "簽名已過有效期";
                }
                else
                {
                    response.Code = CodeConfig.CodeSuccess;
                    response.Message = "簽名校驗成功";
                }
            }
            else
            {
                response.Message = "簽名校驗未通過";
            }
            return response;
        }

創建幾個類 :請求類和響應類和實體類

    /// <summary>
    /// 接口請求類
    /// </summary>
    public class CommRequest<T>
    { 
        //簽名
        public string Sign { get; set; }

        //授權Key
        public string Key { get; set; }

        //操作類型:Query、Write
        public string Type { get; set; }

        //查詢對象
        public T Data { get; set; }
    }

 

    /// <summary>
    /// 接口響應類
    /// </summary>
    public class CommResponse<T>
    {
        public int Code { get; set; }

        public string Message { get; set; }

        public T Data { get; set; }
    }

 

這個用戶類我用來當查詢條件和返回json

    /// <summary>
    /// 用戶類
    /// </summary>
    public class UserInfo
    {
        public string ID { get; set; }

        public string Name { get; set; }

        public string Phone { get; set; }

        public string Address { get; set; }
    }

在ApiModule.cs中在定義一個初始化返回CommResponse的json方法

        /// <summary>
        /// 初始化一個Commresponse的Json
        /// </summary>
        /// <param name="code">返回代碼</param>
        /// <param name="msg">描述</param>
        /// <param name="data">數據</param>
        /// <returns></returns>
        public string InitReturnResponseJson(int code, string msg, object data = null)
        {
            CommResponse<object> response = new CommResponse<object>();
            response.Code = code;
            response.Message = msg;
            response.Data = data;
            return JsonHelper.ObjectConvertJson(response);
        }

 

五:在正式的數據訪問接口中 調用驗證簽名的方法

            //查詢方法
            Post["queryUser"] = p =>
            {
                string param = Request.Query["param"];
                string json = Request.Query["json"];
                string result = string.Empty;
                CommRequest<UserInfo> request = null;
                CommResponse<object> response;
                try
                {
                    request = JsonHelper.JsonConvertObject<CommRequest<UserInfo>>(param);
                    request.Data = JsonHelper.JsonConvertObject<UserInfo>(json);
                    //驗證簽名
                    response = VerificationSign(request.Type, request.Key, json, request.Sign);
                    if (response.Code == CodeConfig.CodeFailed)
                    {
                        return Response.AsText(JsonHelper.ObjectConvertJson(response), "application/json");
                    }
                }
                catch
                {
                    result = InitReturnResponseJson(CodeConfig.CodeFailed, "Json參數格式錯誤");
                    return Response.AsText(result, "application/json");
                }

                //進入接口,開始進行數據操作
                //response = QueryUserInfo(request.Data.ID);
                //result = JsonHelper.ObjectConvertJson(response);


                //返回數據
                response.Code = CodeConfig.CodeSuccess;
                response.Message = "請求成功";
                response.Data= new { Id = request.Data.ID, Name = "Tom", Address = "四川省成都市" };
                result = JsonHelper.ObjectConvertJson(response);

                return Response.AsText(result, "application/json");
            };

 

六:測試查詢接口

  1.當輸入錯誤的簽名或者當發送的json查詢參數被抓取后篡改  都是無法通過服務器簽名驗證的  (在調用此數據接口時,應先獲取sign簽名,見上面!!)

 

  2.或者當簽名正確,但簽名已過設置的2分鍾有效期。也是無法正常訪問接口的

 

  3.當參數完全正確和簽名通過后  則可以拿到數據

 

 

 

 

附: 寫一個測試Demo 

class Program
    {
        static string url = "http://localhost:56157/";  //接口地址
        static string method = string.Empty; //接口方法

        static void Main(string[] args)
        {
            string key = "T8951SLI02UTY983CVBAX03"; //接口的授權Key
            Dictionary<string, string> pars = new Dictionary<string, string>(); //參數

            //請求接口
            //  ①創建查詢參數Json對象
            Console.WriteLine("創建接口查詢參數...");
            var dataJson = JsonHelper.ObjectConvertJson(new { ID = "10001" });
            //  ②獲取此次請求簽名
            Console.WriteLine("開始獲取簽名...");
            CommResponse<string> sign = getApiSign("Query", dataJson);
            if (sign.Code == 1)
            {
                Console.WriteLine("簽名獲取成功:" + sign.Data);
                //獲取簽名成功則繼續請求數據接口
                //  ③創建簽名Json
                var signJson = JsonHelper.ObjectConvertJson(new { Key = key, Sign = sign.Data, Type = "Query" });
                //  ④封裝參數開始請求
                pars.Add("param", signJson);
                pars.Add("json", dataJson);
                method = "queryUser";
                Console.WriteLine("開始發起查詢請求...");
                string result = Http.SendRequest(url + method, pars);

                Console.WriteLine("請求結果:" + result);
            }
            else
            {
                //獲取簽名失敗
                Console.WriteLine("簽名獲取失敗:" + sign.Message);
            }


            Console.ReadKey();
        }

        /// <summary>
        /// 獲取請求簽名
        /// </summary>
        /// <param name="type">操作類型</param>
        /// <param name="data">要請求的數據</param>
        /// <returns></returns>
        public static CommResponse<string> getApiSign(string type, string data)
        {
            Dictionary<string, string> pars = new Dictionary<string, string>(); //參數
            string key = "T8951SLI02UTY983CVBAX03"; //接口的授權Key
            method = "getSign";
            pars.Add("key", key);
            pars.Add("type", type);
            pars.Add("data", data);
            string result = Http.SendRequest(url + method, pars);
            if (!string.IsNullOrWhiteSpace(result))
            {
                return JsonHelper.JsonConvertObject<CommResponse<string>>(result);
            }
            else
            {
                return new CommResponse<string>() { Code = 2, Message = "獲取失敗" };
            }
        }
    }

 

Http請求類

public class Http
    {
        /// <summary>
        /// 發起請求
        /// </summary>
        /// <param name="url">接口地址</param>
        /// <param name="pars">字典類型的參數集合</param>
        /// <param name="timeout"></param>
        /// <returns></returns>
        public static string SendRequest(string url, IDictionary<string, string> pars, int timeout = 120)
        {
            HttpWebRequest request = CreateRequest(url);

            byte[] pdata = Encoding.UTF8.GetBytes(BuildQuery(pars, "utf-8"));

            request.ContentLength = pdata.Length;

            Stream writer = null;
            try
            {
                writer = request.GetRequestStream();
            }
            catch (Exception ex)
            {
                return ex.Message;
            }

            writer.Write(pdata, 0, pdata.Length);
            writer.Close();

            HttpWebResponse response = null;
            try
            {
                //獲得響應流
                response = (HttpWebResponse)request.GetResponse();
            }
            catch (Exception ex)
            {
                return ex.Message;
            }
            StreamReader sRead = new StreamReader(response.GetResponseStream());

            string postContent = sRead.ReadToEnd();
            sRead.Close();

            return postContent;
        }


        /// <summary>
        /// 創建一個Http請求
        /// </summary>
        /// <param name="url">接口地址</param>
        /// <param name="timeout">超時時間:秒</param>
        /// <returns></returns>
        public static HttpWebRequest CreateRequest(string url, int timeout = 30)
        {
            HttpWebRequest mRequest = (HttpWebRequest)WebRequest.Create(url);

            mRequest.Proxy = null;
            mRequest.UseDefaultCredentials = false;
            mRequest.AllowWriteStreamBuffering = true;
            mRequest.Headers[HttpRequestHeader.AcceptLanguage] = "zh-CN";
            mRequest.Headers[HttpRequestHeader.AcceptEncoding] = "gzip, deflate";
            mRequest.Headers[HttpRequestHeader.CacheControl] = "no-cache";
            mRequest.Accept = "*/*";
            mRequest.Method = "POST";
            mRequest.ContentType = "application/x-www-form-urlencoded; charset=utf-8";
            mRequest.ProtocolVersion = HttpVersion.Version11;
            mRequest.ServicePoint.Expect100Continue = false;
            mRequest.ServicePoint.ConnectionLimit = 20000;
            mRequest.ServicePoint.MaxIdleTime = 20000;
            mRequest.ServicePoint.ReceiveBufferSize = 16384;
            mRequest.PreAuthenticate = true;
            mRequest.AllowAutoRedirect = false;
            mRequest.Timeout = timeout * 1000;
            mRequest.ReadWriteTimeout = timeout * 1000;
            mRequest.KeepAlive = true;
            return mRequest;
        }

        /// <summary>
        /// 組裝普通文本請求參數。
        /// </summary>
        /// <param name="parameters">Key-Value形式請求參數字典</param>
        /// <returns>URL編碼后的請求數據</returns>
        static string BuildQuery(IDictionary<string, string> parameters, string encode)
        {
            StringBuilder postData = new StringBuilder();
            bool hasParam = false;
            IEnumerator<KeyValuePair<string, string>> dem = parameters.GetEnumerator();
            while (dem.MoveNext())
            {
                string name = dem.Current.Key;
                string value = dem.Current.Value;
                // 忽略參數名或參數值為空的參數
                if (!string.IsNullOrEmpty(name))
                {
                    if (hasParam)
                    {
                        postData.Append("&");
                    }
                    postData.Append(name);
                    postData.Append("=");
                    if (encode == "gb2312")
                    {
                        postData.Append(System.Web.HttpUtility.UrlEncode(value, Encoding.GetEncoding("gb2312")));
                    }
                    else if (encode == "utf8")
                    {
                        postData.Append(System.Web.HttpUtility.UrlEncode(value, Encoding.UTF8));
                    }
                    else
                    {
                        postData.Append(value);
                    }
                    hasParam = true;
                }
            }
            return postData.ToString();
        }
    }

 

開始斷點發起請求    (直接按正規流程先執行一次看看結果)

接口是正常請求拿到了數據的

 

下面我們來模擬下當發起請求時,查詢參數被惡意修改 (看上面代碼能知道 我們請求簽名時的數據 ID是10001  而我們數據請求時改成了10002)

修改后  請求結果

 

 

在斷點情況下模擬下簽名過有效期

 

 

接口請求安全大概就到這里,另外除此之外 還可以引用一些 限流框架,限制某個IP地址在規定時間內的訪問次數。

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM