1. 程式人生 > >使用者登入及API介面設計

使用者登入及API介面設計

原文:http://www.cnblogs.com/wuhuacong/p/4620300.html#!comments
這篇思路可以算目前主流設計方案,其中可能產生的疑惑點解析:
1、如果是web分離開發祕鑰可以採用非對稱加密;
2、安全性上面採用https(Ps:在安全性要求不是非常嚴格的情況下,不用TLS的“安全”,都是掩耳盜鈴)

---------------------------------------華麗的分割線----------------------------------------

1、Web API的介面訪問分類
Web API介面的訪問方式,大概可以分為幾類:

1)一個是使用使用者令牌,通過Web API介面進行資料訪問。這種方式,可以有效識別使用者的身份,為使用者介面返回使用者相關的資料,如包括使用者資訊維護、密碼修改、或者使用者聯絡人等與使用者身份相關的資料。

2)一種是使用安全簽名進行資料提交。這種方式提交的資料,URL連線的簽名引數是經過安全一定規則的加密的,伺服器收到資料後也經過同樣規則的安全加密,確認資料沒有被中途篡改後,再進行資料修改處理。因此我們可以為不同接入方式,如Web/APP/Winfrom等不同接入方式指定不同的加密祕鑰,但是祕鑰是雙方約定的,並不在網路連線上傳輸,連線傳輸的一般是這個接入的AppID,伺服器通過這個AppID來進行簽名引數的加密對比,這種方式,類似微信後臺的回撥處理機制,它們就是經過這樣的處理。

3)一種方式是提供公開的介面呼叫,不需要傳入使用者令牌、或者對引數進行加密簽名的,這種介面一般較少,只是提供一些很常規的資料顯示而已。

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

在這裡插入圖片描述

2、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,返回給呼叫端即可。
在這裡插入圖片描述

3、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移除就可以了。

4、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);
        }
    }

5、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 介面開發的需要,專門弄的一個除錯工具,可以自動組裝相關的引數,包括使用安全簽名的引數,還可以把所有引數資料進行儲存。
在這裡插入圖片描述