標籤:密碼 帳號 ota 程式 div .com 方式 cti 簽名
原文地址:http://www.cnblogs.com/hubro/p/6248353.html
在各種手機APP泛濫的現在,背後都有同樣泛濫的API介面在支撐,其中魚龍混雜,直接裸奔的WEB API大量存在,安全性令人堪優
在以前WEB API概念沒有很普及的時候,都採用自已定義的介面和結構,對於公開訪問的介面,專業點的都會做下安全驗證,資料簽名之類
反而現在,誰都可以用WEB API估介面,安全性早忘一邊了,特別是外包小公司的APP項目,80%都有安全性漏洞(面試了大半年APP開發得出的結論)
特在過年之前,整理了下在用的解決方案,本方案解決了
- 資料安全問題
- 標準訊息結構
- 介面測試程式
- 介面文檔體現
本文
資料結構
對於一個介面,返回的內容除了要返回業務資料外,還得返回處理狀態,並且這個狀態是在每個介面都得有
所以資料格式都會定義為:
資料頭(描述資料資訊)
-----------------------------------
資料體(具體資料)
本文定義結構為
/// <summary> /// 處理結果 /// </summary> public class DealResult { /// <summary> /// 處理結果 /// </summary> public bool Result { get; set; } /// <summary> /// 訊息 /// </summary> public string Message { get; set; } /// <summary> /// 關聯資料 /// </summary> public object Data { get; set; } }
所有介面都返回此對象,會描述本次請求的狀態,和對應的資料,服務端則根據實際情況,返回處理結果和對應的資料
資料安全
開方式介面安全性就不用多說了,解決方案為加密,或資料簽名驗證,本文方案為進行資料簽名
同返回的資料一樣,提交到伺服器的資料格式也統一約定,定義一個資料頭基類
/// <summary> /// 參數基類 /// </summary> [Serializable] public class ParameBase { string time = DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss"); /// <summary> /// 時間 格式 yyyy-MM-dd hh:mm:ss /// </summary> public string Time { get { return time; } set { time = value; } } /// <summary> /// 來源網站 = 1, IOS = 2,Android = 3, = 4 /// </summary> public int SourceFrom { get; set; } /// <summary> /// 簽名 /// </summary> public string Token { get; set; } }
一個登入對象表示為
/// <summary> /// 登入 /// </summary> public class Login : ParameBase { /// <summary> /// 使用者名稱 /// </summary> public string Name { get; set; } /// <summary> /// 密碼 /// </summary> public string Password { get; set; } }
資料簽名表示為(KEY稍後講到)
Token=MD5(屬性值1+值2....+KEY)
按此對象表示為 MD5(Name+PassWord+Source+Time+KEY)
如果是GET參數怎麼辦,一樣,按參數名計算,同時傳遞的參數要附帶上Source,Time,Token
密鑰機制
有的喜歡把密鑰放在用戶端,或固定密鑰,顯然都有安全問題,解決方案是動態擷取
這就意味著在設計介面時,有一個介面是首先要調用的,讓伺服器返回密鑰,於是就有了登入的概念
過程表示為
登入>返回使用者資訊和密鑰=>儲存使用者資訊和密鑰=>使用密鑰調用其它介面
這樣只有登入者和伺服器才知道自已的密鑰了
綜上所述,資料結構表示為
用戶端提交結構為 ParameBase(附帶簽名資訊)
服務端返回結構為 DealResult
登入機制
同網頁請求一樣,怎麼知道多次調用是同一個人呢,這裡採用了COOKIE的形式,登入後服務端返回一個COOKIE,用戶端再請求時帶上這個COOKIE
服務端需要儲存這個COOKIE標識,所有的驗證處理都會基於此標識來判斷使用者
有了上面基礎,進入項目階段
WEB API項目
其實用什麼項目類型都行,只是WEB API方便了對象結構序列化和傳參
預設WEB API路由RESUFUL形式,沒有控制器方法,只能按METHOD來定義,很不方便,改成控制器的形式,這樣就能用方法名來訪問了
更改路由配置為
12345 |
config.Routes.MapHttpRoute( name: "DefaultApi" , routeTemplate: "api/{controller}/{action}/{id}" , //加上路由ACTION參數 defaults: new { id = RouteParameter.Optional } ); |
在此文,資料分為請求和返回,以登入返回使用者資訊為例,登入狀態請求,使用者資訊為返回,樣本對象結構為
使用者物件
/// <summary> /// 登入返回使用者 /// </summary> public class User { /// <summary> /// 使用者編號 /// </summary> public int Id { get; set; } /// <summary> /// 名稱 /// </summary> public string Name { get; set; } /// <summary> /// 本次登入的KEY /// </summary> public string Key { get; set; } /// <summary> /// 本資登入的憑證 /// </summary> public string Voucher { get; set; } }
請求方式
這裡只採用了GET,POST兩種方式,根據實際情況定義,控制器方法一定需要都標明,不然會出現路由BUG
定義登入方法
/// <summary> /// 登入 /// </summary> /// <param name="parame"></param> /// <returns>User</returns> [HttpPost] [AnonymousSign] public DealResult Login([FromBody] Login parame) { if (parame.Password != "123") { return DealResult(false, "密碼不正確"); } string key2 = System.Guid.NewGuid().ToString(); string voucher = System.Guid.NewGuid().ToString(); var user = new User() { Name = parame.Name, Id = 1, Key = key2, Voucher = voucher }; var timeDiff = (DateTime.Now - Convert.ToDateTime(parame.Time)).TotalSeconds;//儲存用戶端和服務端時間差 LoginStatusContext.SetLoginStatus(voucher, user.Id, key2, timeDiff); CoreHelper.CookieHelper.AddCookies("user", voucher);//存入COOKIE return DealResult(true, "", user); }
這裡可以看到,建立了兩個GUID,一個為使用者憑證,一個為使用者密鑰,放入使用者資訊返回,同時調用LoginStatusContext.SetLoginStatus儲存登入資訊
同時使用了AnonymousSign標註,此方法使用預設簽名Setting.DefaultKey
定義擷取用資訊方法
/// <summary> /// 基本資料 /// </summary> /// <param name="name">參數name</param> /// <returns>User</returns> [HttpGet] public DealResult GetBasicInfo(string name) { var user = new User() { Name = name, Id = CurrentUserId }; return DealResult(true, string.Empty, user); }
樣本控制器完整定義
/// <summary> /// 帳號操作 /// </summary> [SignCheckAttribute] public class AccountController : BaseController { /// <summary> /// 登入 /// </summary> /// <param name="parame"></param> /// <returns>User</returns> [HttpPost] [AnonymousSign] public DealResult Login([FromBody] Login parame) { if (parame.Password != "123") { return DealResult(false, "密碼不正確"); } string key2 = System.Guid.NewGuid().ToString(); string voucher = System.Guid.NewGuid().ToString(); var user = new User() { Name = parame.Name, Id = 1, Key = key2, Voucher = voucher }; var timeDiff = (DateTime.Now - Convert.ToDateTime(parame.Time)).TotalSeconds;//儲存用戶端和服務端時間差 LoginStatusContext.SetLoginStatus(voucher, user.Id, key2, timeDiff); CoreHelper.CookieHelper.AddCookies("user", voucher);//存入COOKIE return DealResult(true, "", user); } /// <summary> /// 基本資料 /// </summary> /// <param name="name">參數name</param> /// <returns>User</returns> [HttpGet] public DealResult GetBasicInfo(string name) { var user = new User() { Name = name, Id = CurrentUserId }; return DealResult(true, string.Empty, user); } /// <summary> /// 測試異常 /// </summary> /// <returns></returns> [HttpGet] public DealResult TestException() { int a = 0; var b = 10 / a; return DealResult(true); } }
此控制器標註了SignCheckAttribute用以進行簽名判斷
具體實現可看SignCheckAttribute代碼
SignCheckAttribute裡實現了有
- 資料簽名判斷
- 簽名逾時判斷
- 使用者登入限制
- 簽名重複使用處理(一個簽名只能使用一次)
- 到期登入使用者處理(沒有主動退出使用者清理)
為了統一處理異常,配置了異常處理
1 |
GlobalConfiguration.Configuration.Filters.Add( new ExceptionAttribute()); |
對介面進行測試
大殺器來了,配合此方案放出了對應的測試載入器,雖然WEB API有個擴充,但沒法對此方案測試
使用此工具能方便按方案要求調用介面,為了方便參數拼接,POST和GET都採用URL參數的形式輸入
測試登入/api/account/login
測試擷取資訊/api/account/GetBasicInfo
測試異常處理/api/account/TestException
在未登入情況下調用擷取資訊
介面文檔
介面結構文檔一直是很讓人頭疼的事,手寫更改了又得維護,版本不一樣還麻煩,自動產生最好了,同樣WEB API 帶擴充沒法表示此結構詳細
大殺器2號來了,按代碼注釋動態產生介面文檔,文檔格式與控制器保持一致
Home控制器代碼實現
public ActionResult Index(SummaryAnalysis.ExportType exportType = SummaryAnalysis.ExportType.NONE) { if (exportType != SummaryAnalysis.ExportType.NONE) { var str = SummaryAnalysis.Load(exportType); return File(str, "application/octet-stream", "Model_" + exportType + ".zip"); } else { if (string.IsNullOrEmpty(outPut)) { outPut = SummaryAnalysis.Load(exportType); } ViewBag.OutPut = outPut; return View(); } } }
在見過的開發文檔,我覺得這是最好的展現形式了,還有錨點,快速定位到對象結構,並且與原始碼保持一致
附WEB API 內建文檔產生區別
附上項目源碼
http://pan.baidu.com/s/1c2rDacK
項目結構:
----------WPF測試程式
----------介面樣本
雖然跟CRL快速開發架構無關,但還是加上CRL的名,好文要頂!
【轉】整套完整安全的API介面解決方案