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

來源:互聯網
上載者:User
  原文地址:WebApi 使用TOKEN+簽名驗證 一、不進行驗證的方式

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);        }    }

 詳細地址:http://www.cnblogs.com/MR-YY/p/5972380.html#!comments








聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.