国产av日韩一区二区三区精品,成人性爱视频在线观看,国产,欧美,日韩,一区,www.成色av久久成人,2222eeee成人天堂

WebApi安全性 使用TOKEN+簽名驗(yàn)證

Original 2016-11-07 10:25:44 1001
abstract:首先問(wèn)大家一個(gè)問(wèn)題,你在寫(xiě)開(kāi)放的API接口時(shí)是如何保證數(shù)據(jù)的安全性的?先來(lái)看看有哪些安全性問(wèn)題在開(kāi)放的api接口中,我們通過(guò)http Post或者Get方式請(qǐng)求服務(wù)器的時(shí)候,會(huì)面臨著許多的安全性問(wèn)題,例如:請(qǐng)求來(lái)源(身份)是否合法?請(qǐng)求參數(shù)被篡改?請(qǐng)求的唯一性(不可復(fù)制),防止請(qǐng)求被惡意攻擊為了保證數(shù)據(jù)在通信時(shí)的安全性,我們可以采用TOKEN+參數(shù)簽名的方式來(lái)進(jìn)行相關(guān)驗(yàn)證。比如說(shuō)我們客戶端需要查詢

首先問(wèn)大家一個(gè)問(wèn)題,你在寫(xiě)開(kāi)放的API接口時(shí)是如何保證數(shù)據(jù)的安全性的?先來(lái)看看有哪些安全性問(wèn)題在開(kāi)放的api接口中,我們通過(guò)http Post或者Get方式請(qǐng)求服務(wù)器的時(shí)候,會(huì)面臨著許多的安全性問(wèn)題,例如:

請(qǐng)求來(lái)源(身份)是否合法?

請(qǐng)求參數(shù)被篡改?

請(qǐng)求的唯一性(不可復(fù)制),防止請(qǐng)求被惡意攻擊

為了保證數(shù)據(jù)在通信時(shí)的安全性,我們可以采用TOKEN+參數(shù)簽名的方式來(lái)進(jìn)行相關(guān)驗(yàn)證。

比如說(shuō)我們客戶端需要查詢產(chǎn)品信息這個(gè)操作來(lái)進(jìn)行分析,客戶端點(diǎn)擊查詢按鈕==》調(diào)用服務(wù)器端api進(jìn)行查詢==》服務(wù)器端返回查詢結(jié)果

一、不進(jìn)行驗(yàn)證的方式

api查詢接口:

客戶端調(diào)用:http://api.XXX.com/getproduct?id=value1

如上,這種方式簡(jiǎn)單粗暴,在瀏覽器直接輸入"http://api.XXX.com/getproduct?id=value1",即可獲取產(chǎn)品列表信息了,但是這樣的方式會(huì)存在很?chē)?yán)重的安全性問(wèn)題,沒(méi)有進(jìn)行任何的驗(yàn)證,大家都可以通過(guò)這個(gè)方法獲取到產(chǎn)品列表,導(dǎo)致產(chǎn)品信息泄露。
那么,如何驗(yàn)證調(diào)用者身份呢?如何防止參數(shù)被篡改呢?如何保證請(qǐng)求的唯一性? 如何保證請(qǐng)求的唯一性,防止請(qǐng)求被惡意攻擊呢?

二、使用TOKEN+簽名認(rèn)證 保證請(qǐng)求安全性

token+簽名認(rèn)證的主要原理是:1.做一個(gè)認(rèn)證服務(wù),提供一個(gè)認(rèn)證的webapi,用戶先訪問(wèn)它獲取對(duì)應(yīng)的token

                                                 2.用戶拿著相應(yīng)的token以及請(qǐng)求的參數(shù)和服務(wù)器端提供的簽名算法計(jì)算出簽名后再去訪問(wèn)指定的api

                3.服務(wù)器端每次接收到請(qǐng)求就獲取對(duì)應(yīng)用戶的token和請(qǐng)求參數(shù),服務(wù)器端再次計(jì)算簽名和客戶端簽名做對(duì)比,如果驗(yàn)證通過(guò)則正常訪問(wèn)相應(yīng)的api,驗(yàn)證失敗則返回具體的失敗信息

具體代碼如下 :

1.用戶請(qǐng)求認(rèn)證服務(wù)GetToken,將TOKEN保存在服務(wù)器端緩存中,并返回對(duì)應(yīng)的TOKEN到客戶端(該請(qǐng)求不需要進(jìn)行簽名認(rèn)證)

public HttpResponsemessage GetToken(string staffId)
        {
            ResultMsg resultMsg = null;
            int id = 0;
 
            //判斷參數(shù)是否合法
            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.客戶端調(diào)用服務(wù)器端API,需要對(duì)請(qǐng)求進(jìn)行簽名認(rèn)證,簽名方式如下 

(1) get請(qǐng)求:按照請(qǐng)求參數(shù)名稱將所有請(qǐng)求參數(shù)按照字母先后順序排序得到:keyvaluekeyvalue...keyvalue  字符串如:將arong=1,mrong=2,crong=3 排序?yàn)椋篴rong=1, crong=3,mrong=2  然后將參數(shù)名和參數(shù)值進(jìn)行拼接得到參數(shù)字符串: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();
 
            // 第二步:把所有參數(shù)名和參數(shù)值串在一起
            StringBuilder query = new StringBuilder("");  //簽名字符串
            StringBuilder queryStr = new StringBuilder(""); //url參數(shù)
            if (parames == null || parames.Count == 0)
                return new Tuple<string,string>("","");
 
            while (dem.MoveNext())
            {
                string key = dem.Current.Key;
                string value =http://www.cnblogs.com/MR-YY/p/ 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請(qǐng)求:將請(qǐng)求的參數(shù)對(duì)象序列化為json格式字符串

Product product = new Product() { Id = 1, Name = "安慕希", Count = 10, Price = 58.8 };
 var data=http://www.cnblogs.com/MR-YY/p/JsonConvert.SerializeObject(product);

(2)在請(qǐng)求頭中添加timespan(時(shí)間戳),nonce(隨機(jī)數(shù)),staffId(用戶Id),signature(簽名參數(shù))

//加入頭信息
            request.Headers.Add("staffid", staffId.ToString()); //當(dāng)前請(qǐng)求用戶StaffId
            request.Headers.Add("timestamp", timeStamp); //發(fā)起請(qǐng)求時(shí)的時(shí)間戳(單位:毫秒)
            request.Headers.Add("nonce", nonce); //發(fā)起請(qǐng)求時(shí)的時(shí)間戳(單位:毫秒)
            request.Headers.Add("signature", GetSignature(timeStamp,nonce,staffId,data)); //當(dāng)前請(qǐng)求內(nèi)容的數(shù)字簽名

(3)根據(jù)請(qǐng)求參數(shù)計(jì)算本次請(qǐng)求的簽名,用timespan+nonc+staffId+token+data(請(qǐng)求參數(shù)字符串)得到signStr簽名字符串,然后再進(jìn)行排序和MD5加密得到最終的signature簽名字符串,添加到請(qǐng)求頭中

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,員工編號(hào)為:" +staffId);
            }
 
            var hash = system.Security.Cryptography.MD5.Create();
            //拼接簽名數(shù)據(jù)
            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);
            //把二進(jìn)制轉(zhuǎn)化為大寫(xiě)的十六進(jìn)制
            StringBuilder result = new StringBuilder();
            foreach (var c in md5Val)
            {
                result.Append(c.ToString("X2"));
            }
            return result.ToString().ToUpper();
        }  

(4) webapi接收到相應(yīng)的請(qǐng)求,取出請(qǐng)求頭中的timespan,nonc,staffid,signature 數(shù)據(jù),根據(jù)timespan判斷此次請(qǐng)求是否失效,根據(jù)staffid取出相應(yīng)token判斷token是否失效,根據(jù)請(qǐng)求類型取出對(duì)應(yīng)的請(qǐng)求參數(shù),然后服務(wù)器端按照同樣的規(guī)則重新計(jì)算請(qǐng)求簽名,判斷和請(qǐng)求頭中的signature數(shù)據(jù)是否相同,如果相同的話則是合法請(qǐng)求,正常返回?cái)?shù)據(jù),如果不相同的話,該請(qǐng)求可能被惡意篡改,禁止訪問(wèn)相應(yīng)的數(shù)據(jù),返回相應(yīng)的錯(cuò)誤信息 

 如下使用全局過(guò)濾器攔截所有api請(qǐng)求進(jìn)行統(tǒng)一的處理

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方法不需要進(jìn)行簽名驗(yàn)證
            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;
                }
            }
 
 
            //判斷請(qǐng)求頭是否包含以下參數(shù)
            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();
            }
 
            //根據(jù)請(qǐng)求類型拼接參數(shù)
            NameValueCollection form = HttpContext.Current.Request.QueryString;
            string data = http://www.cnblogs.com/MR-YY/p/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":
                    //第一步:取出所有g(shù)et參數(shù)
                    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();
 
                    // 第三步:把所有參數(shù)名和參數(shù)值串在一起
                    StringBuilder query = new StringBuilder();
                    while (dem.MoveNext())
                    {
                        string key = dem.Current.Key;
                        string value =http://www.cnblogs.com/MR-YY/p/ 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);
        }
    }

然后我們進(jìn)行測(cè)試,檢驗(yàn)api請(qǐng)求的合法性

Get請(qǐng)求:

1.獲取產(chǎn)品數(shù)據(jù),傳遞參數(shù)id=1,name="wahaha"  ,完整請(qǐng)求為http://localhost:14826/api/product/getproduct?id=1&name=wahaha

21.png

2.請(qǐng)求頭添加timespan,staffid,nonce,signature字段

22.png

 3.如圖當(dāng)data里面的值為id1namewahaha的時(shí)候請(qǐng)求頭中的signature和服務(wù)器端計(jì)算出來(lái)的result的值是完全一樣的,當(dāng)我將data修改為id1namewahaha1之后,服務(wù)器端計(jì)算出來(lái)的簽名result和請(qǐng)求頭中提交的signature就不相同了,就表示為不合法的請(qǐng)求了

23.png

4.不合法的請(qǐng)求就會(huì)被識(shí)別為請(qǐng)求參數(shù)已被修改

24.png

合法的請(qǐng)求則會(huì)返回對(duì)應(yīng)的商品信息

25.png

post請(qǐng)求:

1.post對(duì)象序列化為json字符串后提交到后臺(tái),后臺(tái)返回相應(yīng)產(chǎn)品信息

26.png

2.后臺(tái)獲取請(qǐng)求的參數(shù)信息

27.png

3.判斷簽名是否成功,第一次請(qǐng)求簽名參數(shù)signature和服務(wù)器端計(jì)算result完全相同, 然后當(dāng)把請(qǐng)求參數(shù)中count的數(shù)量從10改成100之后服務(wù)器端計(jì)算的result和請(qǐng)求簽名參數(shù)signature不同,所以請(qǐng)求不合法,是非法請(qǐng)求,同理如果其他任何參數(shù)被修改最后計(jì)算的結(jié)果都會(huì)和簽名參數(shù)不同,請(qǐng)求同樣識(shí)別為不合法請(qǐng)求

28.png

總結(jié):

通過(guò)上面的案例,我們可以看出,安全的關(guān)鍵在于參與簽名的TOKEN,整個(gè)過(guò)程中TOKEN是不參與通信的,所以只要保證TOKEN不泄露,請(qǐng)求就不會(huì)被偽造。

然后我們通過(guò)timestamp時(shí)間戳用來(lái)驗(yàn)證請(qǐng)求是否過(guò)期,這樣就算被人拿走完整的請(qǐng)求鏈接也是無(wú)效的。

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


Release Notes

Popular Entries