引數傳遞機制之JWT
1. 什麼是 JWT
JWT 其全稱為:JSON Web Token,簡單地說就是 JSON 在 Web 上的一種帶簽名的標記形式。官方的定義如下:
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物件在各方之間安全地傳輸資訊。
2. 有什麼作用
對資訊進行簽名之後再進行傳輸有什麼作用,JWT 就有什麼作用。它能起的作用,決定了在專案的需求中是否有必要使用它,它自身的本質決定了它適合的場景。
本質上,JWT 跟自己對資訊加個簽名沒有區別。
那使用它的理由是什麼呢?
(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=*×tamp=*&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
更多細節可參考:https://www.cnblogs.com/cjsblog/p/9277677.h