來源:https://www.cnblogs.com/frank0812/p/11161573.html
1.什麼是JWT Token
JWT(Json Web Tokens) 是一個開放標準(RFC 7519),它定義了一種簡潔,自包含,JSON 對象形式的安全傳遞信息的方法。JWT常用在 Web 應用或者移動應用上,Token是令牌的意思,表示只有拿著令牌才具有一些權限。JWT的聲明(Claim)一般被用來在身份提供者和服務提供者間傳遞身份驗證信息,也可以增加一些額外的其它業務邏輯所必須的聲明信息。
JWT的使用場景
一次性驗證
比如用戶註冊後需要發一封郵件讓其激活賬戶,通常郵件中需要有一個鏈接,這個鏈接需要具備以下的特性:能夠標識用戶,該鏈接具有時效性(通常只允許幾小時之內激活),不能被篡改以激活其他可能的賬戶…這種場景就和 jwt 的特性非常貼近,jwt 的 payload 中固定的參數:iss 簽發者和 exp 過期時間正是為其做準備的。
restful api 的無狀態認證
使用 jwt 來做 restful api 的身份認證也是值得推崇的一種使用方案。客戶端和服務端共享 secret;過期時間由服務端校驗,客戶端定時刷新。
2.JWT的組成
使用JWT token認證前我們先了解下JWT的組成部分。JWT經過加密處理與校驗處理的字符串,形式 A.B.C
<code>A由JWT頭部信息header加密得到
B由JWT用到的身份驗證信息json數據加密得到
C由A和B加密得到,是校驗部分/<code>
怎麼計算A?
<code>header格式為:
{
"typ": "JWT",
"alg": "HS256"
}/<code>
它就是一個json串,兩個字段是必須的,不能多也不能少。alg字段指定了生成C的算法,默認值是HS256,將header用base64加密,得到A。補充:Base64是一種表示二進制數據的方法。由於2的6次方等於64,所以每6個比特為一個單元,對應某一個可打印字符。三個字節有24個比特,對應於4個Base64單元,即3個字節需要用4個Base64可打印字符來表示。
怎樣計算B?
根據JWT claim set[用base64]加密得到的。claim set是一個json數據,是表明用戶身份的數據,可自行指定字段很靈活,也有固定字段表示特定含義(但不一定要包含特定字段,只是推薦)。一些字段的含義:
<code> {
"iss" :"http://example.org", //非必須。issuer 請求實體,可以是發起請求的用戶的信息,也可是jwt的簽發者。
"iat" : 1356999524, //非必須。issued at。 token創建時間,unix時間戳格式
"exp" :"1548333419", //非必須。expire 指定token的生命週期。unix時間戳格式
"aud" : "http://example.com", //非必須。接收該JWT的一方。
"sub" : "jrocket@example.com", //非必須。該JWT所面向的用戶
"nbf" : 1357000000, //非必須。not before。如果當前時間在nbf裡的時間之前,則Token不被接受;一般都會留一些餘地,比如幾分鐘。
"jti" :'222we', //非必須。JWT ID。針對當前token的唯一標識
"GivenName" : "Jonny", // 自定義字段
"Surname" : "Rocket", // 自定義字段
"Email" : "jrocket@example.com", // 自定義字段
"Role" : ["Manager", "Project Administrator"] // 自定義字段
}/<code>
自定義字段的key是一個string,value是一個json數據。將claim set通過Base64加密後得到B,學名payload(載荷)
怎樣計算C?
將A.B使用HS256加密(其實是用header中指定的算法),當然加密過程中還需要密鑰(secret,自行指定的一個字符串)。加密得到C,學名signature,其實就是一個字符串。
3.JWT的工作過程
借鑑於:https://www.cnblogs.com/lonelyxmas/p/8024006.html
下面我們從一個實例來看如何運用JWT機制實現認證:
1.登錄
第一次認證:第一次登錄,用戶從瀏覽器輸入用戶名/密碼,提交後到服務器的登錄處理的Action層(Login Action);Login Action調用認證服務進行用戶名密碼認證,如果認證通過,Login Action層調用用戶信息服務獲取用戶信息(包括完整的用戶信息及對應權限信息);返回用戶信息後,Login Action從配置文件中獲取Token簽名生成的秘鑰信息,進行Token的生成;生成Token的過程中可以調用第三方的JWT Lib生成簽名後的JWT數據;完成JWT數據簽名後,將其設置到COOKIE對象中,並重定向到首頁,完成登錄過程;2.請求認證
基於Token的認證機制會在每一次請求中都帶上完成簽名的Token信息,這個Token信息可能在Cookie中,也可能在HTTP的Authorization頭中;
3.JWT.Net的使用
使用JWT.Net前要首先通過Nuge(git地址:https://github.com/jwt-dotnet/jwt)t獲取JWT.Net包,如下:
添加了JWT.Net包後就可以使用JWT了,這裡封裝了一個簡單的JWTHelper,代碼如下:
<code> public class JWTHelper
{
static IJwtAlgorithm algorithm = new HMACSHA256Algorithm();//HMACSHA256加密
static IJsonSerializer serializer = new JsonNetSerializer();//序列化和反序列
static IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();//Base64編解碼
static IDateTimeProvider provider = new UtcDateTimeProvider();//UTC時間獲取
#region /////生成JWT
public static string GetJWT(string secret, Dictionary<string> payload)
{
IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
return encoder.Encode(payload, secret);
}
#endregion
#region /////驗證JWT
public static bool ValidateJwt(string secret, string token, out string payload,out string message)
{
bool isValidted = false;
payload = "";
try
{
IJwtValidator validator = new JwtValidator(serializer, provider);//用於驗證JWT的類
IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder);//用於解析JWT的類
payload = decoder.Decode(token, secret, verify: true);//verify:true表示解析JWT時進行驗證,該方法會自動調用validator的Validate()方法,不滿足驗證會拋出異常,因此我們不用寫驗證的方法
isValidted = true;
message = "驗證成功";
}
catch (TokenExpiredException)//如果當前時間大於負載中的過期時間(負荷中的exp),引發Token過期異常
{
message = "Token已經過期了!";
}
catch (SignatureVerificationException)//如果簽名不匹配,引發簽名驗證異常
{
message = "Token簽名不正確!";
}
return isValidted;
}
#endregion
}/<string>/<code>
我們在一個控制檯程序中簡單展示一下JWT.Net的用法,代碼如下:
<code>static void Main(string[] args)
{
//服務端的秘鑰,一般放在配置文件中
const string secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk";
//負荷(payload)
var payload = new Dictionary<string>
{
{ "claim1", 0 },
{ "testStr", "hello" },
{"testObj" ,new { name="111"} },
{ "exp", DateTimeOffset.UtcNow.AddSeconds(2).ToUnixTimeSeconds() }
};
Console.WriteLine("生成JWT--------------------------------------------------------------");
Console.WriteLine();
string token = JWTHelper.GetJWT(secret, payload);
Console.WriteLine($"生成的JWT是:{token}");
Console.WriteLine();
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("校驗JWT--------------------------------------------------------------");
Console.WriteLine();
string message;//解析的消息
string curPayload;//解析獲取的負載
if (JWTHelper.ValidateJwt(secret,token,out curPayload,out message))
{
Console.WriteLine($"解析獲取的負載是:{curPayload}");
}
Console.WriteLine(message);
}
}/<string>/<code>
運行結果如圖所示:
4.一些需要注意的問題
這些問題摘自https://blog.csdn.net/qq_28165595/article/details/80214994
1.jwt token洩露了怎麼辦?
前面的文章下有不少人留言提到這個問題,我則認為這不是問題。傳統的 session+cookie 方案,如果洩露了 sessionId,別人同樣可以盜用你的身份。揚湯止沸不如釜底抽薪,不妨來追根溯源一下,什麼場景會導致你的 jwt 洩露。 遵循如下的實踐可以儘可能保護你的 jwt 不被洩露:使用 https 加密你的應用,返回 jwt 給客戶端時設置 httpOnly=true 並且使用 cookie 而不是 LocalStorage 存儲 jwt,這樣可以防止 XSS 攻擊和 CSRF 攻擊
2. secret如何設計
JWT唯一存儲在服務端的只有一個 secret,個人認為這個 secret 應該設計成和用戶相關的屬性,而不是一個所有用戶公用的統一值。這樣可以有效的避免一些註銷和修改密碼時遇到的窘境。 註銷和修改密碼
傳統的 session+cookie 方案用戶點擊註銷,服務端清空 session 即可,因為狀態保存在服務端。但 jwt 的方案就比較難辦了,因為 jwt 是無狀態的,服務端通過計算來校驗有效性。沒有存儲起來,
1. 僅僅清空客戶端的 cookie,這樣用戶訪問時就不會攜帶 jwt,服務端就認為用戶需要重新登錄。這是一個典型的假註銷,對於用戶表現出退出的行為,實際上這個時候攜帶對應的 jwt 依舊可以訪問系統。 2. 清空或修改服務端的用戶對應的 secret,這樣在用戶註銷後,jwt 本身不變,但是由於 secret 不存在或改變,則無法完成校驗。這也是為什麼將 secret 設計成和用戶相關的原因。 3. 藉助第三方存儲自己管理 jwt 的狀態,可以以 jwt 為 key,實現去 redis 一類的緩存中間件中去校驗存在性。方案設計並不難,但是引入 redis 之後,就把無狀態的 jwt 硬生生變成了有狀態了,違背了 jwt 的初衷。實際上這個方案和 session 都差不多了。 修改密碼則略微有些不同,假設號被到了,修改密碼(是用戶密碼,不是 jwt 的 secret)之後,盜號者在原 jwt 有效期之內依舊可以繼續訪問系統,所以僅僅清空 cookie 自然是不夠的,這時,需要強制性的修改 secret。在我的實踐中就是這樣做的。
3.續簽問題
續約問題可以說是我抵制使用 jwt 來代替傳統 session 的最大原因,因為 jwt 的設計中我就沒有發現它將續簽認為是自身的一個特性。
參考文章:
1.https://blog.csdn.net/mn_kw/article/details/80522565
2.https://www.cnblogs.com/lonelyxmas/p/8024083.html
3.https://blog.csdn.net/qq_28165595/article/details/80214994