接口的安全性主要圍繞Token、Timestamp和Sign三個機制展開設計,保證接口的數據不會被篡改和重複調用。算法
Token受權機制:用戶使用用戶名密碼登陸後服務器給客戶端返回一個Token(一般是UUID),並將Token-UserId以鍵值對的形式存放在緩存服務器中。服務端接收到請求後進行Token驗證,若是Token不存在,說明請求無效。Token是客戶端訪問服務端的憑證。數據庫
時間戳超時機制:用戶每次請求都帶上當前時間的時間戳timestamp,服務端接收到timestamp後跟當前時間進行比對,若是時間差大於必定時間(好比5分鐘),則認爲該請求失效。時間戳超時機制是防護DOS攻擊的有效手段。緩存
簽名機制:將 Token 和 時間戳 加上其餘請求參數再用MD5或SHA-1算法(可根據狀況加點鹽)加密,加密後的數據就是本次請求的簽名sign,服務端接收到請求後以一樣的算法獲得簽名,並跟當前的簽名進行比對,若是不同,說明參數被更改過,直接返回錯誤標識。簽名機制保證了數據不會被篡改。安全
拒絕重複調用(非必須):客戶端第一次訪問時,將簽名sign存放到緩存服務器中,超時時間設定爲跟時間戳的超時時間一致,兩者時間一致能夠保證不管在timestamp限定時間內仍是外 URL都只能訪問一次。若是有人使用同一個URL再次訪問,若是發現緩存服務器中已經存在了本次簽名,則拒絕服務。若是在緩存中的簽名失效的狀況下,有人使用同一個URL再次訪問,則會被時間戳超時機制攔截。這就是爲何要求時間戳的超時時間要設定爲跟時間戳的超時時間一致。拒絕重複調用機制確保URL被別人截獲了也沒法使用(如抓取數據)。服務器
一、客戶端經過用戶名密碼登陸服務器並獲取Tokenapp
二、客戶端生成時間戳timestamp,並將timestamp做爲其中一個參數測試
三、客戶端將全部的參數,包括Token和timestamp按照本身的算法進行排序加密獲得簽名sign網站
四、將token、timestamp和sign做爲請求時必須攜帶的參數加在每一個請求的URL後邊(http://url/request?token=123×tamp=123&sign=123123123)加密
五、服務端寫一個過濾器對token、timestamp和sign進行驗證,只有在token有效、timestamp未超時、緩存服務器中不存在sign三種狀況同時知足,本次請求才有效url
若是有人劫持了請求,並對請求中的參數進行了修改,簽名就沒法經過;
若是有人使用已經劫持的URL進行DOS攻擊,服務器則會由於緩存服務器中已經存在簽名或時間戳超時而拒絕服務,因此DOS攻擊也是不可能的;
若是簽名算法和用戶名密碼都暴露了,那真的須要開光了吧。。。。
實際項目中須要根據業務狀況做出裁剪,好比能夠只使用簽名機制就能夠保證信息不會被篡改,或者定向提供服務的時候只用Token機制就能夠了。
有沒有這樣的接口,誰均可以調用,誰均可以訪問,不受時間空間限制,只要能連上互聯網就能調用,毫無安全可言。
查快遞,查天氣預報,查飛機,火車班次等,都是有公共的接口。
/// <summary> /// 接口對外公開 /// </summary> /// <returns></returns> [HttpGet] [Route("NoSecure")] public HttpResponseMessage NoSecure(int age) { var result = new ResultModel<object>() { ReturnCode = 0, Message = string.Empty, Result = string.Empty }; var dataResult = stulist.Where(T => T.Age == age).ToList(); result.Result = dataResult; return GetHttpResponseMessage(result); }
你寫個接口,只想讓特定的調用方使用,咱們把調用的人叫到一個小屋子,給他們每人一把鑰匙。這把鑰匙就是參數加密規則,有了這個規則就能調用。
這有安全問題啊,某個成員不當心丟了鑰匙或者被人竊取,掌握鑰匙的人是否是也能夠來掉用接口了呢?並且能夠複製不少鑰匙給不明不白的人用。
至關於有人拿到了你的請求連接,若是業務沒有對連接惟一性作判斷(實際上業務邏輯一般不會把每次請求的加密簽名記錄下來,因此不會作惟一性判斷),就會被重複調用,有必定安全漏洞,怎麼破?
/// <summary> /// 接口加密 /// </summary> /// <returns></returns> [HttpGet] [Route("SecureBySign")] public HttpResponseMessage SecureBySign([FromUri]int age, long _timestamp, string appKey, string _sign) { var result = new ResultModel<object>() { ReturnCode = 0, Message = string.Empty, Result = string.Empty }; #region 校驗簽名是否合法 var param = new SortedDictionary<string, string>(new AsciiComparer()); param.Add("age", age.ToString()); param.Add("appKey", appKey); param.Add("_timestamp", _timestamp.ToString()); string currentSign = SignHelper.GetSign(param, appKey); if (_sign != currentSign) { result.ReturnCode = -2; result.Message = "簽名不合法"; return GetHttpResponseMessage(result); } #endregion var dataResult = stulist.Where(T => T.Age == age).ToList(); result.Result = dataResult; return GetHttpResponseMessage(result); }
繼上一步,你天天給他們換一把鑰匙。和往常同樣,小偷煞費苦心,準備在一個月黑風高的夜晚動手。拿出鑰匙,搗鼓了半天也沒法開啓你的神聖之門,由於小偷不知道你每天都在換新鑰匙。
小偷不服,在一次得到鑰匙以後,當天就動手了,由於他知道他手裏的鑰匙在次日你更換鑰匙後就失效了。
/// <summary> /// 接口加密並根據時間戳判斷有效性 /// </summary> /// <returns></returns> [HttpGet] [Route("SecureBySign/Expired")] public HttpResponseMessage SecureBySign_Expired([FromUri]int age, long _timestamp, string appKey, string _sign) { var result = new ResultModel<object>() { ReturnCode = 0, Message = string.Empty, Result = string.Empty }; #region 判斷請求是否過時---假設過時時間是20秒 DateTime requestTime = GetDateTimeByTicks(_timestamp); if (requestTime.AddSeconds(20) < DateTime.Now) { result.ReturnCode = -1; result.Message = "接口過時"; return GetHttpResponseMessage(result); } #endregion #region 校驗簽名是否合法 var param = new SortedDictionary<string, string>(new AsciiComparer()); param.Add("age", age.ToString()); param.Add("appKey", appKey); param.Add("_timestamp", _timestamp.ToString()); string currentSign = SignHelper.GetSign(param, appKey); if (_sign != currentSign) { result.ReturnCode = -2; result.Message = "簽名不合法"; return GetHttpResponseMessage(result); } #endregion var dataResult = stulist.Where(T => T.Age == age).ToList(); result.Result = dataResult; return GetHttpResponseMessage(result); }
繼上一步,咋辦呢?你打算下血本,給每一個人配一把鑰匙的基礎上,再給每一個人發個暗號,即便鑰匙被小偷弄去了,小偷沒有暗號,任然沒法如願,並且這樣很容易定位是誰的暗號泄漏問題,找到問題根源
但這個鑰匙還有可能被小偷搞到。
代碼以下:
/// <summary> /// 接口加密並根據時間戳判斷有效性並且帶着私有key校驗 /// </summary> /// <returns></returns> [HttpGet] [Route("SecureBySign/Expired/KeySecret")] public HttpResponseMessage SecureBySign_Expired_KeySecret([FromUri]int age, long _timestamp, string appKey, string _sign) { //key集合,這裏隨便弄兩個測試數據 //若是調用方比較多,須要審覈受權,根據必定的規則生成key把這些數據存放在數據庫中,若是功能擴展開來,能夠針對不一樣的調用方作不一樣的功能權限管理 //在調用接口時動態從庫裏取,每一個調用方在調用時帶上他的key,調用方通常把本身的key放到網站配置中 Dictionary<string, string> keySecretDic = new Dictionary<string, string>(); keySecretDic.Add("key_zhangsan", "D9U7YY5D7FF2748AED89E90HJ88881E6");//張三的key, keySecretDic.Add("key_lisi", "I9O6ZZ3D7FF2748AED89E90ZB7732M9");//李四的key var result = new ResultModel<object>() { ReturnCode = 0, Message = string.Empty, Result = string.Empty }; #region 判斷請求是否過時---假設過時時間是20秒 DateTime requestTime = GetDateTimeByTicks(_timestamp); if (requestTime.AddSeconds(20) < DateTime.Now) { result.ReturnCode = -1; result.Message = "接口過時"; return GetHttpResponseMessage(result); } #endregion #region 根據appkey獲取key值 string secret = keySecretDic.Where(T => T.Key == appKey).FirstOrDefault().Value; #endregion #region 校驗簽名是否合法 var param = new SortedDictionary<string, string>(new AsciiComparer()); param.Add("age", age.ToString()); param.Add("appKey", appKey); param.Add("appSecret", secret);//把secret加入進行加密 param.Add("_timestamp", _timestamp.ToString()); string currentSign = SignHelper.GetSign(param, appKey); if (_sign != currentSign) { result.ReturnCode = -2; result.Message = "簽名不合法"; return GetHttpResponseMessage(result); } #endregion var dataResult = stulist.Where(T => T.Age == age).ToList(); result.Result = dataResult; return GetHttpResponseMessage(result); }
繼上一步,咱們給傳輸機制改成Https,這下小偷完全懵逼了。
那麼問題來了,Https咋玩兒呢?