參數傳遞機制之JWT

1. 什麼是 JWT

JWT 其全稱爲:JSON Web Token,簡單地說就是 JSON 在 Web 上的一種帶簽名的標記形式。官方的定義以下:html

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.算法

即:JSON Web Token (JWT)是一個開放標準(RFC 7519),它定義了一種緊湊的、自包含的方式,用於做爲JSON對象在各方之間安全地傳輸信息。api

2. 有什麼做用

對信息進行簽名以後再進行傳輸有什麼做用,JWT 就有什麼做用。它能起的做用,決定了在項目的需求中是否有必要使用它,它自身的本質決定了它適合的場景。數組

本質上,JWT 跟本身對信息加個簽名沒有區別。安全

那使用它的理由是什麼呢?ide

(1)它創建了一個標準併爲多數人認識和接受,這樣一來就能夠造成標準庫,使用者能夠共享。函數

(2)它造成了一些最佳實踐,這種實踐過程包括了參數安全傳遞的諸多常見方面,如 exp 到期時間屬性的定義來規定簽名有效期等。按照最佳實踐中對一些 JSON 屬性的明肯定義,再加上標準庫對它的貫徹實現,會帶來不少便利。工具

(3)將其做爲 Token 放在請求的 header 中,做爲無狀態的鑑權方式很適合目前多站點應用的場景。測試

但最佳實踐和其特性不能混爲一談,具體到應用場景,仍然能夠利用其特性做適合該場景的其它發揮。編碼

3. 參數訪問控制演化

(1)直接傳參

http://*/api?p1=*&p2=*

這種方式,不進行訪問的權限的判斷,公開可直接訪問。

(2)帶KEY傳參

http://*/api?p1=*&p2=*&key=

這種方式須要知道正確的 KEY 才能訪問,但 KEY 明文附在後面易泄露。

(3)帶簽名傳參

這種方式,將 KEY 做爲簽名算法的加密條件,不明文顯示,不知道 KEY 則沒法生成相應的簽名,感受不錯。不足在於,簽名一次以後訪問連接一直爲有效會帶有風險。

http://*/api?p1=*&p2=*&sign=

其中籤名部分,如採用 md5 方式,key 做爲運算的一部分。

sign=md5(p1+p2+key)

(4)帶時間戳簽名

參數中帶上簽名時的時間戳,時間戳會參與簽名算法,服務端不只檢測簽名的有效性,還會比較時間是否在合理範圍內,如 5 分鐘之內,如此一來,連接在一段時間以後就會失效。

http://*/api?p1=*&p2=*&timestamp=*&sign=

其中籤名部分,如採用 md5 方式,time,key 均做爲運算的一部分。

sign=md5(p1+p2+time+key)

(5)獨立鑑權參數簽名

將鑑權部分獨立出來簽名,這樣的好處就是鑑權部分獨立的判斷過程,其它形參再也不須要參與這個簽名與判斷過程。

參數可以使用 JSON 形式,因而可讓其變成如下形式:

鑑權傳輸部分形式如:{p1:abcd,p2:abcd}.sign

其中,簽名部分,如採用 md5 方式,將 JSON 字符串與 key 拼接運算,而且使用鏈接符.點,以下。

sign=md5({p1:abcd,p2:abcd}.key)

(6)帶頭部的獨立鑑權部分

爲了更加靈活的,將鑑權部分加個頭部。頭部用來幹什麼呢,能夠指定簽名算法,或之後可能要更多擴展參數用,如如下形式。

{alg:MD5}.{p1:abcd,p2:abcd}.sign

簽名部分,爲前兩部分再鏈接上 key 一塊兒運算。

sign=md5({alg:MD5}.{p1:,p2:}.key)

(7)最終標準化爲 JWT 形式

頭部稱之爲 header,數據部分稱之爲 payload,簽名部分爲 signature。

(7.1) header 不使用明文,採用其 base64 形式

(7.2) payload 不使用明文,採用其 base64 形式

(7.3) signature 爲前二者(都是 base64 形式)經過 . 點鏈接,再採用 header 中指定的簽名算法簽名的結果。

(7.4) 最終形式爲 base64(header).base64(payload).signature

(7.5) base64 考慮到URL編碼,將=去掉,+號變成-,/變成_ 處理。

(7.6) 最終字符串經過做爲請求 header 進行傳輸。

4. 最簡實現

給定一個簽名用的 sercretKey 和 payload,生成成符合要求的 JWT 字符串。多數時候,需求可能就是這樣簡單,至於簽名算法,這裏就使用通常默認的HS256。則須要的功能函數大體是:

func getJwt (payload){
    var content = base64({"alg":"HS256","typ":"JWT"}) + . + base64(payload)
    var signature = base46( sign(content, sercretKey)  )
    return content + . + sign
}

C# 的實例代碼,這裏給出一個 C# 的 JWT 輔助類,其中 JObject 引用了 Newtonsoft.Json 包。

public class JWTHelper
    {
        #region 工具函數準備

        /// <summary>
        /// 對字符串 Base64 編碼,而且替換 = + / 爲 "" - _
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public static string Base64URL(string str)
        {
            return Convert.ToBase64String(Encoding.UTF8.GetBytes(str)).Replace("=", "").Replace("+", "-").Replace("/", "_");
        }

        /// <summary>
        /// 對字節數組 Base64 編碼,而且替換 = + / 爲 "" - _
        /// </summary>
        /// <param name="bs"></param>
        /// <returns></returns>
        public static string Base64URL(byte[] bs)
        {
            return Convert.ToBase64String(bs).Replace("=", "").Replace("+", "-").Replace("/", "_");
        }

        /// <summary>
        /// HMAC SHA256
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public static string HS256(string str, string key)
        {
            var encoding = new System.Text.UTF8Encoding();
            byte[] keyByte = encoding.GetBytes(key);
            byte[] messageBytes = encoding.GetBytes(str);
            using (var hmacsha256 = new HMACSHA256(keyByte))
            {
                byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
                return Base64URL(hashmessage);  
            }
        }

        #endregion

        /// <summary>
        /// 生成簽名後的 JWT 最終字符串。
        /// 爲了簡化示例,這裏使用簽名算法就設定爲:HS256
        /// header = {"alg":"HS256","typ":"JWT"}
        /// </summary>
        /// <param name="payload"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        public static string Sign(JObject payload, String key)
        {
            JObject header = new JObject();
            header["alg"] = "HS256";
            header["typ"] = "JWT";

            string h = Base64URL(header.ToString(Formatting.None));
            string p = Base64URL(payload.ToString(Formatting.None));

            string s = HS256(h + "." + p, key);
            return String.Format("{0}.{1}.{2}", h, p, s);
        }
    }

使用如下代碼測試一下:

JObject payload = new JObject();
payload["username"] = "xxx";
Console.Write(JWTHelper.Sign(payload, "123fd"));

獲得結果

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Inh4eCJ9.1C28A5CqMa70FLtUQh4pwSZWPlZhbQ-ZeYs38K_sqks

https://jwt.io/ 上,能夠驗證一下,獲得了一樣的結果。

5. 具體使用

顯然,它也會存在一些問題,如經過 base64 解碼看到明文,或者是在有效期內取得整個 token 進行訪問等。因此使用是根據須要來的。並且,也能夠在 JWT 上進一步加入自定義的新機制來應對更多的場景。

如下這篇文章列出了一些問題與趨勢,可供參考。

https://baijiahao.baidu.com/s?id=1608021814182894637

更多細節可參考:http://www.javashuo.com/article/p-zuvhvdiv-cy.html

相關文章
相關標籤/搜索