Web API應用架構設計分析(2) Web API應用架構設計分析(2) Web API應用架構設計分析(1)

Web API應用架構設計分析(2)

在上篇隨筆《Web API應用架構設計分析(1)》,我對Web API的各類應用架構進行了歸納性的分析和設計,Web API 是一種應用接口框架,它可以構建HTTP服務以支撐更普遍的客戶端(包括瀏覽器,手機和平板電腦等移動設備)的框架,本篇繼續這個主題,介紹如何利用ASP.NET Web API 來設計Web API層以及相關的調用處理。html

一、Web API的接口訪問分類

Web API接口的訪問方式,大概能夠分爲幾類:git

1)一個是使用用戶令牌,經過Web API接口進行數據訪問。這種方式,能夠有效識別用戶的身份,爲用戶接口返回用戶相關的數據,如包括用戶信息維護、密碼修改、或者用戶聯繫人等與用戶身份相關的數據。github

2)一種是使用安全簽名進行數據提交。這種方式提交的數據,URL鏈接的簽名參數是通過安全必定規則的加密的,服務器收到數據後也通過一樣規則的安全加密,確認數據沒有被中途篡改後,再進行數據修改處理。所以咱們能夠爲不一樣接入方式,如Web/APP/Winfrom等不一樣接入方式指定不一樣的加密祕鑰,可是祕鑰是雙方約定的,並不在網絡鏈接上傳輸,鏈接傳輸的通常是這個接入的AppID,服務器經過這個AppID來進行簽名參數的加密對比,這種方式,相似微信後臺的回調處理機制,它們就是通過這樣的處理。web

3)一種方式是提供公開的接口調用,不須要傳入用戶令牌、或者對參數進行加密簽名的,這種接口通常較少,只是提供一些很常規的數據顯示而已。json

下面圖示就是這幾種接入方式的說明和大概應用場景。api

 

二、Web API使用安全簽名的實現

首先咱們爲用戶註冊的時候,須要由咱們承認的終端發起,也就是它們須要進行安全簽名,後臺確認簽名有效性,才能正常實現用戶註冊,不然遭到僞造數據,系統就失去原有的意義了。瀏覽器

複製代碼
    /// <summary>
    /// 註冊用戶信息接口
    /// </summary>
    public interface IUserApi
    {
        /// <summary>
        /// 註冊用戶處理,包括用戶名,密碼,身份證號,手機等信息
        /// </summary>
        /// <param name="json">註冊用戶信息</param>
        /// <param name="signature">加密簽名字符串</param>
        /// <param name="timestamp">時間戳</param>
        /// <param name="nonce">隨機數</param>
        /// <param name="appid">應用接入ID</param>
        /// <returns></returns>
        ResultData Add(UserJson json,
            string signature, string timestamp, string nonce, string appid);
    }
複製代碼

其實咱們得到用戶的令牌,也是須要進行用戶安全簽名認證的,這樣咱們纔有效保證用戶身份令牌獲取的合法性。緩存

複製代碼
    /// <summary>
    /// 系統認證等基礎接口
    /// </summary>
    public interface IAuthApi
    {
        /// <summary>
        /// 註冊用戶獲取訪問令牌接口
        /// </summary>
        /// <param name="username">用戶登陸名稱</param>
        /// <param name="password">用戶密碼</param>
        /// <param name="signature">加密簽名字符串</param>
        /// <param name="timestamp">時間戳</param>
        /// <param name="nonce">隨機數</param>
        /// <param name="appid">應用接入ID</param>
        /// <returns></returns>
        TokenResult GetAccessToken(string username, string password,
            string signature, string timestamp, string nonce, string appid);
    }
複製代碼

上面介紹到的參數,咱們說起了幾個參數,一個是加密簽名字符串,一個是時間戳,一個是隨機數,一個是應用接入ID,咱們通常的處理規則以下所示。安全

1)Web API 爲各類應用接入,如APP、Web、Winform等接入端分配應用AppID以及通訊密鑰AppSecret,雙方各自存儲。
2)接入端在請求Web API接口時需攜帶如下參數:signature、 timestamp、nonce、appid,簽名是根據幾個參數和加密祕鑰生成。
3) Web API 收到接口調用請求時需先檢查傳遞的簽名是否合法,驗證後才調用相關接口。服務器

加密簽名在服務端(Web API端)的驗證流程參考微信的接口的處理方式,處理邏輯以下所示。

1)檢查timestamp 與系統時間是否相差在合理時間內,如10分鐘。
2)將appSecret、timestamp、nonce三個參數進行字典序排序
3)將三個參數字符串拼接成一個字符串進行SHA1加密
4)加密後的字符串可與signature對比,若匹配則標識該次請求來源於某應用端,請求是合法的。

C#端代碼校驗以下所示。

複製代碼
        /// <summary>
        /// 檢查應用接入的數據完整性
        /// </summary>
        /// <param name="signature">加密簽名內容</param>
        /// <param name="timestamp">時間戳</param>
        /// <param name="nonce">隨機字符串</param>
        /// <param name="appid">應用接入Id</param>
        /// <returns></returns>
        public CheckResult ValidateSignature(string signature, string timestamp, string nonce, string appid)
        {
            CheckResult result = new CheckResult();
            result.errmsg = "數據完整性檢查不經過";

            //根據Appid獲取接入渠道的詳細信息
            AppInfo channelInfo = BLLFactory<App>.Instance.FindByAppId(appid);
            if (channelInfo != null)
            {
                #region 校驗簽名參數的來源是否正確
                string[] ArrTmp = { channelInfo.AppSecret, timestamp, nonce };

                Array.Sort(ArrTmp);
                string tmpStr = string.Join("", ArrTmp);

                tmpStr = FormsAuthentication.HashPasswordForStoringInConfigFile(tmpStr, "SHA1");
                tmpStr = tmpStr.ToLower();

                if (tmpStr == signature && ValidateUtil.IsNumber(timestamp))
                {
                    DateTime dtTime = timestamp.ToInt32().IntToDateTime();
                    double minutes = DateTime.Now.Subtract(dtTime).TotalMinutes;
                    if (minutes > timspanExpiredMinutes)
                    {
                        result.errmsg = "簽名時間戳失效";
                    }
                    else
                    {
                        result.errmsg = "";
                        result.success = true;
                        result.channel = channelInfo.Channel;
                    }
                }
                #endregion
            }
            return result;
        }
複製代碼

一旦咱們完成對安全簽名進行成功認證,也就是咱們對數據提交的來源和完整性進行了確認,就能夠進行更多和安全性相關的操做了,如獲取用戶的訪問令牌信息的操做以下所示。

第一步是驗證用戶的簽名是否符合要求,符合要求後進行用戶信息的比對,並生成用戶訪問令牌數據JSON,返回給調用端便可。

 

三、Web API使用安全令牌的實現

經過上面的接口,咱們獲取到的用戶訪問令牌,之後和用戶相關的信息調用,咱們就能夠經過這個令牌參數進行傳遞就能夠了,這個令牌帶有用戶的一些基礎信息,如用戶ID,過時時間等等,這個Token的設計思路來源於JSON Web Token (JWT),具體能夠參考http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html,以及GitHub上的項目https://github.com/jwt-dotnet/jwt

因爲Web API的調用,都是一種無狀態方式的調用方式,咱們經過token來傳遞咱們的用戶信息,這樣咱們只須要驗證Token就能夠了。

JWT的令牌生成邏輯以下所示

令牌生成後,咱們須要在Web API調用處理前,對令牌進行校驗,確保令牌是正確有效的。

檢查的代碼,就是把令牌生成的過程逆反過來,獲取相應的信息,而且對令牌簽發的時間進行有效性判斷,通常能夠約定一個失效時間,如1天或者7天,也不用設置過短。

複製代碼
        /// <summary>
        /// 檢查用戶的Token有效性
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        public CheckResult ValidateToken(string token)
        {
            //返回的結果對象
            CheckResult result = new CheckResult();
            result.errmsg = "令牌檢查不經過";

            if (!string.IsNullOrEmpty(token))
            {
                try
                {
                    string decodedJwt = JsonWebToken.Decode(token, sharedKey);
                    if (!string.IsNullOrEmpty(decodedJwt))
                    {
                        #region 檢查令牌對象內容
                        dynamic root = JObject.Parse(decodedJwt);
                        string username = root.name;
                        string userid = root.iss;
                        int jwtcreated = (int)root.iat;

                        //檢查令牌的有效期,7天內有效
                        TimeSpan t = (DateTime.UtcNow - new DateTime(1970, 1, 1));
                        int timestamp = (int)t.TotalDays;
                        if (timestamp - jwtcreated > expiredDays)
                        {
                            throw new ArgumentException("用戶令牌失效.");
                        }

                        //成功校驗
                        result.success = true;
                        result.errmsg = "";
                        result.userid = userid;
                        #endregion
                    }
                }
                catch (Exception ex)
                {
                    LogTextHelper.Error(ex);
                }
            }
            return result;
        }
複製代碼

通常來講,訪問令牌不能永久有效,對於訪問令牌的從新更新問題,能夠設置一個規則,只容許最新的令牌使用,並把它存儲在接口緩存裏面進行對比,應用系統退出的時候,就把內存裏面的Token移除就能夠了。

四、ASP.NET Web API的開發

 上面咱們定義了通常的Web API接口,以及實現相應的業務實現,若是咱們須要建立Web API層,還須要構建一個Web API項目的。

建立好相應的項目後,能夠爲項目添加一個Web API基類,方便控制共同的接口。

而後咱們就能夠在Controller目錄上建立更多的應用API控制器了。

最後咱們爲了統一全部的API接口都是返回JSON方式,咱們須要對WebApiConfig裏面的代碼進行設置下。

複製代碼
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API 配置和服務
            config.SetCorsPolicyProviderFactory(new CorsPolicyFactory());
            config.EnableCors();

            // Web API 路由
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{action}/{id}",
                defaults: new { action = "post", id = RouteParameter.Optional }
            );

            // Remove the JSON formatter
            //config.Formatters.Remove(config.Formatters.JsonFormatter);

            // Remove the XML formatter
            config.Formatters.Remove(config.Formatters.XmlFormatter);
        }
    }
複製代碼

五、Web API 接口的測試

接下來咱們要作的就是須要增長業務接口,以便進行具體的測試了,建議使用Winform項目,對每一個接口進行一個測試,或者也能夠考慮使用單元測試的方式,看我的喜愛吧。

例如咱們若是要測試用戶登錄的接口的話,咱們的測試代碼以下所示。

複製代碼
        /// <summary>
        /// 生成簽名字符串
        /// </summary>
        /// <param name="appSecret">接入祕鑰</param>
        /// <param name="timestamp">時間戳</param>
        /// <param name="nonce">隨機數</param>
        private string SignatureString(string appSecret, string timestamp, string nonce)
        {
            string[] ArrTmp = { appSecret, timestamp, nonce };

            Array.Sort(ArrTmp);
            string tmpStr = string.Join("", ArrTmp);

            tmpStr = FormsAuthentication.HashPasswordForStoringInConfigFile(tmpStr, "SHA1");
            return tmpStr.ToLower();
        }

        private TokenResult GetTokenResult()
        {
            string timestamp = DateTime.Now.DateTimeToInt().ToString();
            string nonce = new Random().NextDouble().ToString();
            string signature = SignatureString(appSecret, timestamp, nonce);

            string appended = string.Format("&signature={0}&timestamp={1}&nonce={2}&appid={3}", signature, timestamp, nonce, appId);
            string queryUrl = url + "Auth/GetAccessToken?username=test&password=123456" + appended;

            HttpHelper helper = new HttpHelper();
            string token = helper.GetHtml(queryUrl);
            Console.WriteLine(token);
            TokenResult tokenResult = JsonConvert.DeserializeObject<TokenResult>(token);
            return tokenResult;
        }
複製代碼

若是咱們已經得到了令牌,咱們根據令牌傳遞參數給鏈接,並獲取其餘數據的測試處理代碼以下所示。

複製代碼
            //獲取訪問令牌
            TokenResult tokenResult = GetTokenResult();

            string queryUrl = url + "/Contact/get?token=" + tokenResult.access_token;
            HttpHelper helper = new HttpHelper();
            string result = helper.GetHtml(queryUrl);
            Console.WriteLine(result);
複製代碼

若是須要POST數據的話,那麼調用代碼以下所示。

複製代碼
            //使用POST方式
            var data = new
            {
                name = "張三",
                certno = "123456789",
            };
            var postData = data.ToJson();

            queryUrl = url + "/Contact/Add?token=" + tokenResult.access_token;
            helper = new HttpHelper();
            helper.ContentType = "application/json";
            result = helper.GetHtml(queryUrl, postData, true);
            Console.WriteLine(result);
複製代碼

Web API後臺,會自動把POST的JSON數據轉換爲對應的對象的。

若是是GET方式,咱們可能能夠直接經過瀏覽器進行調試,若是是POST方式,咱們須要使用一些協助工具,如Fiddler等處理工具,可是最好的方式是本身根據須要弄一個測試工具,方便測試。

如下就是我爲了本身Web API 接口開發的須要,專門弄的一個調試工具,能夠自動組裝相關的參數,包括使用安全簽名的參數,還能夠把全部參數數據進行存儲。

 

主要研究技術:代碼生成工具、會員管理系統、客戶關係管理軟件、病人資料管理軟件、Visio二次開發、酒店管理系統、倉庫管理系統等共享軟件開發
專一於Winform開發框架/混合式開發框架Web開發框架Bootstrap開發框架微信門戶開發框架的研究及應用
相關文章
相關標籤/搜索