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)直接傳參
這種方式,不進行訪問的許可權的判斷,公開可直接訪問。
(2)帶KEY傳參
這種方式需要知道正確的 KEY 才能訪問,但 KEY 明文附在後面易洩露。
(3)帶簽名傳參
這種方式,將 KEY 作為簽名演算法的加密條件,不明文顯示,不知道 KEY 則無法生成相應的簽名,感覺不錯。不足在於,簽名一次之後訪問連結一直為有效會帶有風險。
其中籤名部分,如採用 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 上進一步加入自定義的新機制來應對更多的場景。
以下這篇文章列出了一些問題與趨勢,可供參考。