乾貨來襲-整套完整安全的API接口解決方案

在各類手機APP氾濫的如今,背後都有一樣氾濫的API接口在支撐,其中魚龍混雜,直接裸奔的WEB API大量存在,安全性使人堪優面試

在之前WEB API概念沒有很普及的時候,都採用自已定義的接口和結構,對於公開訪問的接口,專業點的都會作下安全驗證,數據簽名之類api

反而如今,誰均可以用WEB API估接口,安全性早忘一邊了,特別是外包小公司的APP項目,80%都有安全漏洞(面試了大半年APP開發得出的結論)安全

特在過年以前,整理了下在用的解決方案,本方案解決了服務器

  • 數據安全問題
  • 標準消息結構
  • 接口測試程序
  • 接口文檔體現

正文微信

數據結構

對於一個接口,返回的內容除了要返回業務數據外,還得返回處理狀態,而且這個狀態是在每一個接口都得有數據結構

因此數據格式都會定義爲:app

數據頭(描述數據信息)框架

-----------------------------------ide

數據體(具體數據)工具

本文定義結構爲

/// <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來定義,很不方便,改爲控制器的形式,這樣就能用方法名來訪問了

更改路由配置爲

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裏實現了有

  • 數據簽名判斷
  • 簽名超時判斷
  • 用戶登陸限制
  • 簽名重複使用處理(一個簽名只能使用一次)
  • 過時登陸用戶處理(沒有主動退出用戶清理)

爲了統一處理異常,配置了異常處理

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的名,好文要頂!

相關文章
相關標籤/搜索