WebApi安全性 使用TOKEN+簽名驗證

來源:互聯網
上載者:User

標籤:cron   web   span   按鈕   md5   ota   數值   exec   timestamp   

首先問大家一個問題,你在寫開放的API介面時是如何保證資料的安全性的?先來看看有哪些安全性問題在開放的api介面中,我們通過http Post或者Get方式請求伺服器的時候,會面臨著許多的安全性問題,例如:

  1. 請求來源(身份)是否合法?
  2. 請求參數被篡改?
  3. 請求的唯一性(不可複製),防止請求被惡意攻擊

為了保證資料在通訊時的安全性,我們可以採用TOKEN+參數簽名的方式來進行相關驗證。

 

 

比如說我們用戶端需要查詢產品資訊這個操作來進行分析,用戶端點擊查詢按鈕==》調用伺服器端api進行查詢==》伺服器端返回查詢結果

一、不進行驗證的方式

api查詢介面:

用戶端調用:http://api.XXX.com/getproduct?id=value1

如上,這種方式簡單粗暴,在瀏覽器直接輸入"http://api.XXX.com/getproduct?id=value1",即可擷取產品列表資訊了,但是這樣的方式會存在很嚴重的安全性問題,沒有進行任何的驗證,大家都可以通過這個方法擷取到產品列表,導致產品資訊泄露。
那麼,如何驗證調用者身份呢?如何防止參數被篡改呢?如何保證請求的唯一性? 如何保證請求的唯一性,防止請求被惡意攻擊呢?

 

二、使用TOKEN+簽名認證 保證請求安全性

token+簽名認證的主要原理是:1.做一個認證服務,提供一個認證的webapi,使用者先訪問它擷取對應的token

                                         2.使用者拿著相應的token以及請求的參數和伺服器端提供的簽名演算法計算出簽名後再去訪問指定的api

                3.伺服器端每次接收到請求就擷取對應使用者的token和請求參數,伺服器端再次計算簽名和用戶端簽名做對比,如果驗證通過則正常訪問相應的api,驗證失敗則返回具體的失敗資訊

 

具體代碼如下 :

1.使用者請求認證服務GetToken,將TOKEN儲存在伺服器端緩衝中,並返回對應的TOKEN到用戶端(該請求不需要進行簽名認證)

public HttpResponseMessage GetToken(string staffId)        {            ResultMsg resultMsg = null;            int id = 0;            //判斷參數是否合法            if (string.IsNullOrEmpty(staffId) || (!int.TryParse(staffId, out id)))            {                resultMsg = new ResultMsg();                resultMsg.StatusCode = (int)StatusCodeEnum.ParameterError;                resultMsg.Info = StatusCodeEnum.ParameterError.GetEnumText();                resultMsg.Data = "";                return HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));            }            //插入緩衝            Token token =(Token)HttpRuntime.Cache.Get(id.ToString());            if (HttpRuntime.Cache.Get(id.ToString()) == null)            {                token = new Token();                token.StaffId = id;                token.SignToken = Guid.NewGuid();                token.ExpireTime = DateTime.Now.AddDays(1);                HttpRuntime.Cache.Insert(token.StaffId.ToString(), token, null, token.ExpireTime, TimeSpan.Zero);            }            //返回token資訊            resultMsg =new ResultMsg();            resultMsg.StatusCode = (int)StatusCodeEnum.Success;            resultMsg.Info = "";            resultMsg.Data = token;            return HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));        }

 

2.用戶端調用伺服器端API,需要對請求進行簽名認證,簽名方式如下 

(1) get請求:按照請求參數名稱將所有請求參數按照字母先後順序排序得到:keyvaluekeyvalue...keyvalue  字串如:將arong=1,mrong=2,crong=3 排序為:arong=1, crong=3,mrong=2  然後將參數名和參數值進行拼接得到參數字串:arong1crong3mrong2。  

public static Tuple<string,string> GetQueryString(Dictionary<string, string> parames)        {            // 第一步:把字典按Key的字母順序排序            IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parames);            IEnumerator<KeyValuePair<string, string>> dem = sortedParams.GetEnumerator();            // 第二步:把所有參數名和參數值串在一起            StringBuilder query = new StringBuilder("");  //簽名字串            StringBuilder queryStr = new StringBuilder(""); //url參數            if (parames == null || parames.Count == 0)                return new Tuple<string,string>("","");            while (dem.MoveNext())            {                string key = dem.Current.Key;                string value = dem.Current.Value;                if (!string.IsNullOrEmpty(key))                {                    query.Append(key).Append(value);                    queryStr.Append("&").Append(key).Append("=").Append(value);                }            }            return new Tuple<string, string>(query.ToString(), queryStr.ToString().Substring(1, queryStr.Length - 1));        }

 

post請求:將請求的參數對象序列化為json格式字串   

 Product product = new Product() { Id = 1, Name = "安慕希", Count = 10, Price = 58.8 }; var data=JsonConvert.SerializeObject(product);

 

(2)在要求標頭中添加timespan(時間戳記),nonce(隨機數),staffId(使用者Id),signature(簽名參數)   

            //加入頭資訊            request.Headers.Add("staffid", staffId.ToString()); //當前請求使用者StaffId            request.Headers.Add("timestamp", timeStamp); //發起請求時的時間戳記(單位:毫秒)            request.Headers.Add("nonce", nonce); //發起請求時的時間戳記(單位:毫秒)            request.Headers.Add("signature", GetSignature(timeStamp,nonce,staffId,data)); //當前請求內容的數位簽章

 

(3)根據請求參數計算本次請求的簽名,用timespan+nonc+staffId+token+data(請求參數字串)得到signStr簽名字串,然後再進行排序和MD5加密得到最終的signature簽名字串,添加到要求標頭中


 
private static string GetSignature(string timeStamp,string nonce,int staffId,string data)        {            Token token = null;            var resultMsg = GetSignToken(staffId);            if (resultMsg != null)            {                if (resultMsg.StatusCode == (int)StatusCodeEnum.Success)                {                    token = resultMsg.Result;                }                else                {                    throw new Exception(resultMsg.Data.ToString());                }            }            else            {                throw new Exception("token為null,員工編號為:" +staffId);            }            var hash = System.Security.Cryptography.MD5.Create();            //拼接簽名資料            var signStr = timeStamp +nonce+ staffId + token.SignToken.ToString() + data;            //將字串中字元按升序排序            var sortStr = string.Concat(signStr.OrderBy(c => c));            var bytes = Encoding.UTF8.GetBytes(sortStr);            //使用MD5加密            var md5Val = hash.ComputeHash(bytes);            //把二進位轉化為大寫的十六進位            StringBuilder result = new StringBuilder();            foreach (var c in md5Val)            {                result.Append(c.ToString("X2"));            }            return result.ToString().ToUpper();        }

 

(4) webapi接收到相應的請求,取出要求標頭中的timespan,nonc,staffid,signature 資料,根據timespan判斷此次請求是否失效,根據staffid取出相應token判斷token是否失效,根據請求類型取出對應的請求參數,然後伺服器端按照同樣的規則重新計算請求籤名,判斷和要求標頭中的signature資料是否相同,如果相同的話則是合法請求,正常返回資料,如果不相同的話,該請求可能被惡意篡改,禁止訪問相應的資料,返回相應的錯誤資訊 

 如下使用全域過濾器攔截所有api請求進行統一的處理

 public class ApiSecurityFilter : ActionFilterAttribute    {        public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)        {            ResultMsg resultMsg = null;            var request = actionContext.Request;            string method = request.Method.Method;            string staffid = String.Empty, timestamp = string.Empty, nonce = string.Empty, signature = string.Empty;            int id = 0;            if (request.Headers.Contains("staffid"))            {                staffid = HttpUtility.UrlDecode(request.Headers.GetValues("staffid").FirstOrDefault());            }            if (request.Headers.Contains("timestamp"))            {                timestamp = HttpUtility.UrlDecode(request.Headers.GetValues("timestamp").FirstOrDefault());            }            if (request.Headers.Contains("nonce"))            {                nonce = HttpUtility.UrlDecode(request.Headers.GetValues("nonce").FirstOrDefault());            }            if (request.Headers.Contains("signature"))            {                signature = HttpUtility.UrlDecode(request.Headers.GetValues("signature").FirstOrDefault());            }            //GetToken方法不需要進行簽名驗證            if (actionContext.ActionDescriptor.ActionName == "GetToken")            {                if (string.IsNullOrEmpty(staffid) || (!int.TryParse(staffid, out id) || string.IsNullOrEmpty(timestamp) || string.IsNullOrEmpty(nonce)))                {                    resultMsg = new ResultMsg();                    resultMsg.StatusCode = (int)StatusCodeEnum.ParameterError;                    resultMsg.Info = StatusCodeEnum.ParameterError.GetEnumText();                    resultMsg.Data = "";                    actionContext.Response = HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));                    base.OnActionExecuting(actionContext);                    return;                }                else                {                    base.OnActionExecuting(actionContext);                    return;                }            }            //判斷要求標頭是否包含以下參數            if (string.IsNullOrEmpty(staffid) || (!int.TryParse(staffid, out id) || string.IsNullOrEmpty(timestamp) || string.IsNullOrEmpty(nonce) || string.IsNullOrEmpty(signature)))            {                resultMsg = new ResultMsg();                resultMsg.StatusCode = (int)StatusCodeEnum.ParameterError;                resultMsg.Info = StatusCodeEnum.ParameterError.GetEnumText();                resultMsg.Data = "";                actionContext.Response = HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));                base.OnActionExecuting(actionContext);                return;            }            //判斷timespan是否有效            double ts1 = 0;            double ts2 = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds;            bool timespanvalidate = double.TryParse(timestamp, out ts1);            double ts = ts2 - ts1;            bool falg = ts > int.Parse(WebSettingsConfig.UrlExpireTime) * 1000;            if (falg || (!timespanvalidate))            {                resultMsg = new ResultMsg();                resultMsg.StatusCode = (int)StatusCodeEnum.URLExpireError;                resultMsg.Info = StatusCodeEnum.URLExpireError.GetEnumText();                resultMsg.Data = "";                actionContext.Response = HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));                base.OnActionExecuting(actionContext);                return;            }            //判斷token是否有效            Token token = (Token)HttpRuntime.Cache.Get(id.ToString());            string signtoken = string.Empty;            if (HttpRuntime.Cache.Get(id.ToString()) == null)            {                resultMsg = new ResultMsg();                resultMsg.StatusCode = (int)StatusCodeEnum.TokenInvalid;                resultMsg.Info = StatusCodeEnum.TokenInvalid.GetEnumText();                resultMsg.Data = "";                actionContext.Response = HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));                base.OnActionExecuting(actionContext);                return;            }            else            {                signtoken = token.SignToken.ToString();            }            //根據請求類型拼接參數            NameValueCollection form = HttpContext.Current.Request.QueryString;            string data = string.Empty;            switch (method)            {                case "POST":                    Stream stream = HttpContext.Current.Request.InputStream;                    string responseJson = string.Empty;                    StreamReader streamReader = new StreamReader(stream);                    data = streamReader.ReadToEnd();                    break;                case "GET":                    //第一步:取出所有get參數                    IDictionary<string, string> parameters = new Dictionary<string, string>();                    for (int f = 0; f < form.Count; f++)                    {                        string key = form.Keys[f];                        parameters.Add(key, form[key]);                    }                    // 第二步:把字典按Key的字母順序排序                    IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parameters);                    IEnumerator<KeyValuePair<string, string>> dem = sortedParams.GetEnumerator();                    // 第三步:把所有參數名和參數值串在一起                    StringBuilder query = new StringBuilder();                    while (dem.MoveNext())                    {                        string key = dem.Current.Key;                        string value = dem.Current.Value;                        if (!string.IsNullOrEmpty(key))                        {                            query.Append(key).Append(value);                        }                    }                    data = query.ToString();                    break;                default:                    resultMsg = new ResultMsg();                    resultMsg.StatusCode = (int)StatusCodeEnum.HttpMehtodError;                    resultMsg.Info = StatusCodeEnum.HttpMehtodError.GetEnumText();                    resultMsg.Data = "";                    actionContext.Response = HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));                    base.OnActionExecuting(actionContext);                    return;            }                        bool result = SignExtension.Validate(timestamp, nonce, id, signtoken,data, signature);            if (!result)            {                resultMsg = new ResultMsg();                resultMsg.StatusCode = (int)StatusCodeEnum.HttpRequestError;                resultMsg.Info = StatusCodeEnum.HttpRequestError.GetEnumText();                resultMsg.Data = "";                actionContext.Response = HttpResponseExtension.toJson(JsonConvert.SerializeObject(resultMsg));                base.OnActionExecuting(actionContext);                return;            }            else            {                base.OnActionExecuting(actionContext);            }        }        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)        {            base.OnActionExecuted(actionExecutedContext);        }    }

 

然後我們進行測試,檢驗api請求的合法性

Get請求:

1.擷取產品資料,傳遞參數id=1,name="wahaha"  ,完整請求為http://localhost:14826/api/product/getproduct?id=1&name=wahaha

 

2.要求標頭添加timespan,staffid,nonce,signature欄位

 

 3.當data裡面的值為id1namewahaha的時候要求標頭中的signature和伺服器端計算出來的result的值是完全一樣的,當我將data修改為id1namewahaha1之後,伺服器端計算出來的簽名result和要求標頭中提交的signature就不相同了,就表示為不合法的請求了

 

4.不合法的請求就會被識別為請求參數已被修改

  合法的請求則會返回對應的商品資訊

post請求:

1.post對象序列化為json字串後提交到後台,後台返回相應產品資訊

 

2.後台擷取請求的參數資訊

3.判斷簽名是否成功,第一次請求籤名參數signature和伺服器端計算result完全相同, 然後當把請求參數中count的數量從10改成100之後伺服器端計算的result和請求籤名參數signature不同,所以請求不合法,是非法請求,同理如果其他任何參數被修改最後計算的結果都會和簽名參數不同,請求同樣識別為不合法請求

 

總結:

通過上面的案例,我們可以看出,安全的關鍵在於參與簽名的TOKEN,整個過程中TOKEN是不參與通訊的,所以只要保證TOKEN不泄露,請求就不會被偽造。

然後我們通過timestamp時間戳記用來驗證請求是否到期,這樣就算被人拿走完整的請求連結也是無效的。

Sign簽名的方式能夠在一定程度上防止資訊被篡改和偽造,保障通訊的安全

 

源碼地址:https://github.com/13138899620/TokenSign

WebApi安全性 使用TOKEN+簽名驗證

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.