安全的API接口解決方案


在各種手機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; } }
View Code

所有接口都返回此對象,會描述本次請求的狀態,和對應的數據,服務端則根據實際情況,返回處理結果和對應的數據

 

數據安全

開方式接口安全性就不用多說了,解決方法為加密,或數據簽名驗證,本文方案為進行數據簽名

同返回的數據一樣,提交到服務器的數據格式也統一約定,定義一個數據頭基類

    /// <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; } }
View Code

 

一個登錄對象表示為

    /// <summary> /// 登錄 /// </summary> public class Login : ParameBase { /// <summary> /// 用戶名 /// </summary> public string Name { get; set; } /// <summary> /// 密碼 /// </summary> public string Password { get; set; } }
View Code

數據簽名表示為(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來定義,很不方便,改成控制器的形式,這樣就能用方法名來訪問了

更改路由配置為

?
1
2
3
4
5
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; } }
View Code

 

請求方式

這里只采用了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); }
View Code

這里可以看到,創建了兩個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); }
View Code

 

示例控制器完整定義

 /// <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); } }
View Code

 

此控制器標注了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的名,好文要頂!


參與討論,QQ群:1582632 密語 CRL

 

相關推薦


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM