WebApi與手機客戶端通訊安全機制

最近公司有幾個項目須要開發手機客戶端,服務器端選用WebApi,那麼如何保證手機客戶端在請求服務器端時數據不被篡改,如何保證一個http請求的失效機制,下面總結一下咱們在項目中針對這兩個問題的解決方案。json

基本思路以下:api

  用戶在成功登錄app客戶端以後,手機客戶端向服務器端發出的全部的http請求在請求頭(HttpHeader)上都會帶上下面三個參數:一、Uid(用戶ID),二、Ts(時間戳),三、Sign(簽名)。其中Ts是當前時間減去1970-1-1獲得的10位的時間時間戳數字,Sign是接口中全部http請求參數與Uid、Ts通過MD5加密後獲得的一個字符串。服務器

具體實現以下(客戶端的實現,手機客戶端生成下面兩個參數的思路是同樣的):app

一、Ts時間戳ide

Ts參數能夠保證請求的時效性,在手機客戶端生成的Ts,在服務器端驗證一下,保證請求是在咱們規定的時間段內,具體代碼以下:post

(1)、生成Ts(C#)代碼以下,Andriod和IOS能夠同理生成測試

 /// <summary>
        /// 獲取十位的時間戳
        /// </summary>
        /// <param name="dt"></param>
        /// <returns></returns>
        public string GenerateTimeStamp(DateTime dt)
        {
            // Default implementation of UNIX time of the current UTC time  
            TimeSpan ts = dt.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalSeconds).ToString();
        }  

(2)、服務器端端驗證Ts代碼以下,咱們規定從手機客戶端發到服務器端的請求有效期爲5分鐘,時間戳參數是跟在Http請求頭中ui

//獲取請求頭信息
            var requestHeader = HttpContext.Current.Request.Headers;
           
            //10位時間戳
            var Ts = requestHeader.Get("Ts");
            //驗證Ts是否合法(請求時間有效時間爲:加減5分鐘)
            var ts = Ts;//10位時間戳
            if (ts.Length != 10)
            {
                var resp = Request.CreateResponse<string>(HttpStatusCode.OK, "請求已過時", "application/json"); ;

                throw new HttpResponseException(resp);
            }
            var tsDate = ComHelper.ConvertIntDateTime(ts.ToString());
            if (tsDate > DateTime.Now.AddMinutes(5) || tsDate < DateTime.Now.AddMinutes(-5))
            {
                var resp = Request.CreateResponse<string>(HttpStatusCode.OK, "請求已過時", "application/json"); ;

                throw new HttpResponseException(resp);
            }

(3)、ComHelper公共類代碼以下加密

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Security;

namespace OpenAPITest.App_Start
{
    public class ComHelper
    {
        /// <summary>
        /// 獲取post/get集合
        /// </summary>
        /// <param name="ignoreCase">true 不區分大小寫,統一返回小寫  false 區分大小寫</param>
        /// <returns></returns>
        public static SortedDictionary<string, string> GetRequestSortDic(bool ignoreCase)
        {
            int i = 0;
            SortedDictionary<string, string> sArray = new SortedDictionary<string, string>();
            NameValueCollection coll;
            //Load Form variables into NameValueCollection variable.
            coll = HttpContext.Current.Request.Form;

            //coll = HttpContext.Current.Request.Params;

            // Get names of all forms into a string array.
            String[] requestItem = coll.AllKeys;
            for (i = 0; i < requestItem.Length; i++)
            {
                if (ignoreCase)
                    sArray.Add(requestItem[i].ToLower(), GetString(requestItem[i]));
                else
                    sArray.Add(requestItem[i], GetString(requestItem[i]));
            }

            coll = HttpContext.Current.Request.QueryString;
            requestItem = coll.AllKeys;
            for (i = 0; i < requestItem.Length; i++)
            {
                if (ignoreCase)
                    sArray.Add(requestItem[i].ToLower(), GetString(requestItem[i]));
                else
                    sArray.Add(requestItem[i], GetString(requestItem[i]));
            }

            return sArray;
        }
        /// <summary>
        /// 從當前環境中獲取
        /// </summary>
        /// <param name="name"></param>
        /// <param name="defValue"></param>
        /// <returns></returns>
        public static string GetString(string name)
        {
            string res = "";
            var v = HttpContext.Current.Request[name];
            if (v != null)
            {
                res = v.ToString();
            }
            return res;
        }

        /// <summary>
        /// 時間戳轉爲C#格式時間
        /// </summary>
        /// <param name="timeStamp"></param>
        /// <returns></returns>
        public static DateTime ConvertIntDateTime(string timeStamp)
        {
            DateTime dtStart = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1));
            long lTime = long.Parse(timeStamp + "0000000");
            TimeSpan toNow = new TimeSpan(lTime); return dtStart.Add(toNow);
        }
        /// <summary>
        /// MD5加密
        /// </summary>
        /// <param name="str">原串</param>
        /// <param name="code">加密位</param>
        /// <returns></returns>
        public static string ToMD5(string str)
        {
            return FormsAuthentication.HashPasswordForStoringInConfigFile(str, "MD5").ToLower();
        }

        /// <summary>
        /// 獲取Sign
        /// </summary>
        /// <param name="inputPara"></param>
        /// <param name="privateKey"></param>
        /// <returns></returns>
        public static string GetResponseMysign(SortedDictionary<string, string> inputPara, string privateKey)
        {
            string fullstring = GetPostStrings(inputPara, "_sign") + privateKey;
            return ToMD5(fullstring);
        }
        private static string GetPostStrings(SortedDictionary<string, string> inputPara, string excepted)
        {
            Dictionary<string, string> sPara = new Dictionary<string, string>();

            //過濾空值、sign與sign_type參數
            foreach (KeyValuePair<string, string> temp in inputPara)
            {
                if (temp.Key.ToLower() != excepted && temp.Value != "" && temp.Value != null)
                {
                    sPara.Add(temp.Key.ToLower(), temp.Value);
                }
            }

            //得到簽名結果
            StringBuilder prestr = new StringBuilder();
            foreach (KeyValuePair<string, string> temp in sPara)
            {
                prestr.Append(temp.Key + "=" + temp.Value + "&");
            }

            //去掉最後一個&字符
            int nLen = prestr.Length;
            if (nLen > 1)
                prestr.Remove(nLen - 1, 1);
            return prestr.ToString();
        }

        /// <summary>
        /// 獲取十位的時間戳
        /// </summary>
        /// <param name="dt"></param>
        /// <returns></returns>
        public static string GenerateTimeStamp(DateTime dt)
        {
            // Default implementation of UNIX time of the current UTC time  
            TimeSpan ts = dt.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalSeconds).ToString();
        }  

    }
}
ComHelper

 

二、Sign簽名url

(1)、sign的生成規則:服務器端接口中的全部參數+Uid+Ts,去除掉參數中值爲空的參數後, 按照參數key值排序,用&連接,並所有轉化爲小寫,而後用MD5加密,經過HttpHeader發送到服務器端接口。

生成Sign大代碼以下(C#),Android和IOS能夠同理生成

假如手機客戶端請求的一個API接口爲:http://weapi.com/order/getlist?StatusID=1&CarID=2&CityID=3&name=&key=222

sign=md5(carid=2&cityid=3&key=222&statusid=1&ts=0123456789&uid=110)

(2)、驗證客戶端的Sign,防止參數被修改

 //請求籤名,客戶端生成的簽名
            var Sign = requestHeader.Get("Sign");
//排序字典,按照key排序
            SortedDictionary<string, string> postValue = null;
            //獲取請求中全部的參數
            postValue = ComHelper.GetRequestSortDic(true);
            postValue.Add("Uid", Uid);//API帳戶名稱
            postValue.Add("Ts", Ts);//10位時間戳 

            string APIPrivateKey = "2D7E7C96-DAC5-4526-96C3-C60CDEC4B120";//客戶端和手機端保持一致
            //服務器端生成的Sign
            string mysign = ComHelper.GetResponseMysign(postValue, APIPrivateKey);
            if (!Sign.Equals(mysign, StringComparison.InvariantCultureIgnoreCase))
            {
                var resp = Request.CreateResponse<string>(HttpStatusCode.OK, "簽名錯誤", "application/json"); ;

                throw new HttpResponseException(resp);
            }

 三、模擬測試

 (1)C#模擬Http請求,代碼以下

 //請求的API地址
            string url = "http://localhost:51942/api/Values/Get?StatusID=1&CarID=2&CityID=3&name=&key=1233";
            //生成Ts
            string Ts = ComHelper.GenerateTimeStamp(DateTime.Now);
            //SortedDictionary會自動按照key值排序
            SortedDictionary<string, string> sortDic = new SortedDictionary<string, string>();
            sortDic.Add("StatusID", "1");
            sortDic.Add("CarID", "2");
            sortDic.Add("CityID", "3");
            sortDic.Add("name", "");
            sortDic.Add("key", "1233");
            sortDic.Add("Uid","110");
            sortDic.Add("Ts", Ts);
            string APIPrivateKey = "2D7E7C96-DAC5-4526-96C3-C60CDEC4B120";//客戶端和手機端保持一致,md5加密多使用了一個參數
            //獲取Sign簽名
            string Sign = ComHelper.GetResponseMysign(sortDic, APIPrivateKey);
            
            //發送請求
            System.Net.WebRequest wReq = System.Net.WebRequest.Create(url);
            wReq.Headers.Add("Uid", "110");
            wReq.Headers.Add("Ts", Ts);
            wReq.Headers.Add("Sign", Sign);

            System.Net.WebResponse wResp = wReq.GetResponse();
            System.IO.Stream respStream = wResp.GetResponseStream();

            using (System.IO.StreamReader reader = new System.IO.StreamReader(respStream, Encoding.UTF8))
            {
                string res= reader.ReadToEnd();
            }

 

(2)服務器端驗證參數,參數驗證寫在BaseApiController.cs文件中,只要繼承該類的均可以驗證客戶端傳過來的參數

 public class ValuesController : BaseApiController
    {
        
        // GET api/values
        public IEnumerable<string> Get(string StatusID,string CarID,string CityID,string name, string key)
        {
            return new string[] { "value1", "value2" };
        }

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Net.Http;
using System.Net;
namespace OpenAPITest.App_Start
{
    public class BaseApiController : ApiController
    {
        /// <summary>
        /// 初始化方法
        /// </summary>
        /// <param name="controllerContext"></param>
        protected override void Initialize(HttpControllerContext controllerContext)
        {
            base.Initialize(controllerContext);
            //獲取請求頭信息
            var requestHeader = HttpContext.Current.Request.Headers;
            //登陸用戶的ID
            var Uid = requestHeader.Get("Uid");
            //請求籤名
            var Sign = requestHeader.Get("Sign");
            //10位時間戳
            var Ts = requestHeader.Get("Ts");

            //驗證請求頭信息是否合法
            string errorMsg = CheckRequestHeader(Sign, Ts, Uid);
            //拋出錯誤信息
            if (!string.IsNullOrWhiteSpace(errorMsg))
            {
                var resp = Request.CreateResponse<string>(HttpStatusCode.OK, "認證失敗", "application/json"); ;
                throw new HttpResponseException(resp);
            }
            //驗證Ts是否合法(請求時間有效時間爲:加減5分鐘)
            var ts = Ts;//10位時間戳
            if (ts.Length != 10)
            {
                var resp = Request.CreateResponse<string>(HttpStatusCode.OK, "請求已過時", "application/json"); ;

                throw new HttpResponseException(resp);
            }
            var tsDate = ComHelper.ConvertIntDateTime(ts.ToString());
            if (tsDate > DateTime.Now.AddMinutes(5) || tsDate < DateTime.Now.AddMinutes(-5))
            {
                var resp = Request.CreateResponse<string>(HttpStatusCode.OK, "請求已過時", "application/json"); ;

                throw new HttpResponseException(resp);
            }

            //排序字典,按照key排序
            SortedDictionary<string, string> postValue = null;
            //獲取請求中全部的參數
            postValue = ComHelper.GetRequestSortDic(true);
            postValue.Add("Uid", Uid);//API帳戶名稱
            postValue.Add("Ts", Ts);//10位時間戳 

            string APIPrivateKey = "2D7E7C96-DAC5-4526-96C3-C60CDEC4B120";//客戶端和手機端保持一致
            //服務器端生成的Sign
            string mysign = ComHelper.GetResponseMysign(postValue, APIPrivateKey);
            if (!Sign.Equals(mysign, StringComparison.InvariantCultureIgnoreCase))
            {
                var resp = Request.CreateResponse<string>(HttpStatusCode.OK, "簽名錯誤", "application/json"); ;

                throw new HttpResponseException(resp);
            }
            //驗證經過


        }

        /// <summary>
        /// 驗證請求頭
        /// </summary>
        /// <param name="Sign"></param>
        /// <param name="Ts"></param>
        /// <param name="Pid"></param>
        /// <returns></returns>
        private string CheckRequestHeader(string Sign, string Ts, string Uid)
        {
            string ErrorMsg = "";
            //請求籤名
            if (string.IsNullOrWhiteSpace(Sign))
            {
                ErrorMsg = "認證失敗";
            }
            //10位時間戳 
            else if (string.IsNullOrWhiteSpace(Ts))
            {
                ErrorMsg = "認證失敗";
            }
            //API帳戶名稱
            else if (string.IsNullOrWhiteSpace(Uid))
            {
                ErrorMsg = "認證失敗";
            }
            return ErrorMsg;
        }
    }
}
BaseApiController

 

做者: Eric.Chen
出處: https://www.cnblogs.com/lc-chenlong
若是喜歡做者的文章,請關注「寫代碼的猿」訂閱號以便第一時間得到最新內容。本文版權歸做者全部,歡迎轉載
相關文章
相關標籤/搜索