asp net core 2.1中如何使用jwt(從原理到精通)
為什么使用 Jwt
最近,移動開發(fā)的勁頭越來越足,學(xué)校搞的各種比賽都需要用手機 APP 來撐場面,所以,作為寫后端的,很有必要改進一下以往的基于 Session 的身份認證方式了,理由如下:
- 移動端經(jīng)常要保持長時間(1 到 2 星期)在線,但是 Session 卻不好在服務(wù)端保存這么久,雖然可以持久化到數(shù)據(jù)庫,但是還是挺費資源
- 移動端往往不是使用的網(wǎng)頁技術(shù),所以藏在 Cookie 里面的 SessionId 不是很方便的傳遞給服務(wù)端
- 服務(wù)端暴露給客戶端的接口往往是 RESTful 風(fēng)格的,這是一種無狀態(tài)的 API 風(fēng)格,所以身份認證的方式最好也是無狀態(tài)的才好
所以我選擇了使用 Jwt (Json Web Token) 這個技術(shù)。Jwt 是一種無狀態(tài)的分布式的身份驗證方式,與 Session 相反,Jwt 將用戶信息存放在 Token 的 payload 字段保存在客戶端,通過 RSA 加密的方式,保證數(shù)據(jù)不會被篡改,驗證數(shù)據(jù)有效性。
下面是一個使用 Jwt 的系統(tǒng)的身份驗證流程:

可以看出,用戶的信息保存在 Token 中,而 Token 分布在用戶的設(shè)備中,所以服務(wù)端不再需要在內(nèi)存中保存用戶信息了
身份認證的 Token 傳遞時以一種相當(dāng)簡單的格式保存在 header 中,方便客戶端對其進行操作
原理
jwt對所有語言都是通用的,只要知道秘鑰,另一一種語言有可以對jwt的有效性進行判斷;
jwt的組成;Header部分Base64轉(zhuǎn)化.Payload部分Base64轉(zhuǎn)化.使用HS256方式根據(jù)秘鑰對前面兩部分進行加密后再Base64轉(zhuǎn)化,其中使用的hs256加密是header部分指定的,也可以通過官網(wǎng)的查看,如下圖:

原理就這么簡單,那究竟用怎樣使用C#來實現(xiàn)呢,又怎么確定它的正確性呢?,請繼續(xù)
使用C#實現(xiàn)
我們定義一個今天方法,其中需要使用到Microsoft.IdentityModel.Tokens.dll,asp.net core 2.1再帶,如果其他版本,沒有自帶,需要nuget 一下這個類庫
/// <summary>
/// 創(chuàng)建jwttoken,源碼自定義
/// </summary>
/// <param name="payLoad"></param>
/// <param name="header"></param>
/// <returns></returns>
public static string CreateToken(Dictionary<string, object> payLoad,int expiresMinute, Dictionary<string, object> header = null)
{
if (header == null)
{
header = new Dictionary<string, object>(new List<KeyValuePair<string, object>>() {
new KeyValuePair<string, object>("alg", "HS256"),
new KeyValuePair<string, object>("typ", "JWT")
});
}
//添加jwt可用時間(應(yīng)該必須要的)
var now = DateTime.UtcNow;
payLoad["nbf"] = ToUnixEpochDate( now);//可用時間起始
payLoad["exp"] = ToUnixEpochDate(now.Add(TimeSpan.FromMinutes(expiresMinute)));//可用時間結(jié)束
var encodedHeader = Base64UrlEncoder.Encode(JsonConvert.SerializeObject(header));
var encodedPayload = Base64UrlEncoder.Encode(JsonConvert.SerializeObject(payLoad));
var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(securityKey));
var encodedSignature = Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(encodedHeader, ".", encodedPayload))));
var encodedJwt = string.Concat(encodedHeader, ".", encodedPayload, ".", encodedSignature);
return encodedJwt;
}
public static long ToUnixEpochDate(DateTime date) => (long)Math.Round((date.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds);
該方法很簡單,只需要傳入header鍵值對和payLoad鍵值對,然后根據(jù)原理進行Base64轉(zhuǎn)換和hs256加密,接下來我們來使用一個測試類對其進行測試,代碼如下:
[TestMethod]
public void TokenValidateTest()
{
Dictionary<string, object> payLoad = new Dictionary<string, object>();
payLoad.Add("sub", "rober");
payLoad.Add("jti", "09e572c7-62d0-4198-9cce-0915d7493806");
payLoad.Add("nbf", null);
payLoad.Add("exp", null);
payLoad.Add("iss", "roberIssuer");
payLoad.Add("aud", "roberAudience");
payLoad.Add("age", 30);
var encodeJwt = TokenContext.CreateToken(payLoad, 30);
var result = TokenContext.Validate(encodeJwt, (load) => { return true; });
Assert.IsTrue(result);
}
先不管后面的驗證,我們先看看其中生成的encodeJwt的值:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJyb2JlciIsImp0aSI6IjY0OWMyYjUxLTE4ZGQtNDEzYy05Yzc5LTI4NWNhMDAxODU2NSIsIm5iZiI6MTU0MDYxMDY2NSwiZXhwIjoxNTQwNjEyNDY1LCJpc3MiOiJyb2Jlcklzc3VlciIsImF1ZCI6InJvYmVyQXVkaWVuY2UiLCJhZ2UiOjMwfQ.7Is2KYHAtSr5fW2gPU1jGeHPzz2ULCZJGcWb40LSYyw

第一部分和第二部分,并不是加密,只是Base64轉(zhuǎn)換,我們可以通過其他語言輕松轉(zhuǎn)換回來,如下使用javascript進行轉(zhuǎn),window.atob(base64加密) window.btoa(base64解密)
var header=JSON.parse(window.atob('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'))
如下圖:

我再對payLoa進行轉(zhuǎn)換回來, var payLoad=JSON.parse(window.atob('eyJzdWIiOiJyb2JlciIsImp0aSI6IjY0OWMyYjUxLTE4ZGQtNDEzYy05Yzc5LTI4NWNhMDAxODU2NSIsIm5iZiI6MTU0MDYxMDY2NSwiZXhwIjoxNTQwNjEyNDY1LCJpc3MiOiJyb2Jlcklzc3VlciIsImF1ZCI6InJvYmVyQXVkaWVuY2UiLCJhZ2UiOjMwfQ')) ,如下圖:

所以,從這里可以看出來,Base64并不是屬于加密,只是簡單轉(zhuǎn)換,因此,不能在payLoad中存放重要內(nèi)容,比如密碼等
使用aspnetcore 中自帶的類生成jwt
aspnet core中自帶了一個jwt幫助類,其實原理一樣,對上面做了封裝,豐富了一個內(nèi)容,我們繼續(xù)使用一個靜態(tài)方法,如下
/// <summary>
/// 創(chuàng)建jwtToken,采用微軟內(nèi)部方法,默認使用HS256加密,如果需要其他加密方式,請更改源碼
/// 返回的結(jié)果和CreateToken一樣
/// </summary>
/// <param name="payLoad"></param>
/// <param name="expiresMinute">有效分鐘</param>
/// <returns></returns>
public static string CreateTokenByHandler(Dictionary<string, object> payLoad, int expiresMinute)
{
var now = DateTime.UtcNow;
// Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims.
// You can add other claims here, if you want:
var claims = new List<Claim>();
foreach (var key in payLoad.Keys)
{
var tempClaim = new Claim(key, payLoad[key]?.ToString());
claims.Add(tempClaim);
}
// Create the JWT and write it to a string
var jwt = new JwtSecurityToken(
issuer: null,
audience: null,
claims: claims,
notBefore: now,
expires: now.Add(TimeSpan.FromMinutes(expiresMinute)),
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(securityKey)), SecurityAlgorithms.HmacSha256));
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
return encodedJwt;
}
它效果和上面一模一樣,如果使用同樣的header 、payload、秘鑰,生成的jwt肯定一樣,這里就不演示了,感興趣的可以自行嘗試;
aspnetcore中如何使用自定義jwt驗證
上面講了那么多,只是為了大家更好的理解如何使用jwt進行驗證,那是jwt是如何進行驗證的呢?,如果一個http請求過來,一般jwt攜帶在http請求頭部的Authorization中;先不看如何獲取,先看看他是如何驗證的,我們再定義個靜態(tài)方法,如下:
/// <summary>
/// 驗證身份 驗證簽名的有效性,
/// </summary>
/// <param name="encodeJwt"></param>
/// <param name="validatePayLoad">自定義各類驗證; 是否包含那種申明,或者申明的值, </param>
/// 例如:payLoad["aud"]?.ToString() == "roberAuddience";
/// 例如:驗證是否過期 等
/// <returns></returns>
public static bool Validate(string encodeJwt,Func<Dictionary<string,object>,bool> validatePayLoad)
{
var success = true;
var jwtArr = encodeJwt.Split('.');
var header = JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlEncoder.Decode(jwtArr[0]));
var payLoad = JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlEncoder.Decode(jwtArr[1]));
var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(securityKey));
//首先驗證簽名是否正確(必須的)
success = success && string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1])))));
if (!success)
{
return success;//簽名不正確直接返回
}
//其次驗證是否在有效期內(nèi)(也應(yīng)該必須)
var now = ToUnixEpochDate(DateTime.UtcNow);
success = success && (now >= long.Parse(payLoad["nbf"].ToString()) && now < long.Parse(payLoad["exp"].ToString()));
//再其次 進行自定義的驗證
success = success && validatePayLoad(payLoad);
return success;
}
其中validatePayLoad 參數(shù)是一個自定義的驗證的Fun,執(zhí)行該Fun方法時會把解密后的payload作為參數(shù)傳入進去
我們驗證通過分為兩部分,
第一,必須的(自認為的)
- jwt簽名是否正確,請看以上代碼實現(xiàn)
- jwt是否在可以時間內(nèi),請看以上代碼實現(xiàn)
第二,自定義的(各復(fù)雜的,原理就是獲取payLoad 的某個值,然后對這個值進行各種判讀--等于,大于,包含,)
- 該jwt是不是進入黑名單
- aud==‘roberAudience'
我們來通過一個測試類驗證
[TestMethod]
public void TokenCustomerValidateTest()
{
Dictionary<string, object> payLoad = new Dictionary<string, object>();
payLoad.Add("sub", "rober");
payLoad.Add("jti", Guid.NewGuid().ToString());
payLoad.Add("nbf", null);
payLoad.Add("exp", null);
payLoad.Add("iss", "roberIssuer");
payLoad.Add("aud", "roberAudience");
payLoad.Add("age", 30);
var encodeJwt = TokenContext.CreateToken(payLoad, 30);
var result = TokenContext.Validate(encodeJwt, (load) => {
var success = true;
//驗證是否包含aud 并等于 roberAudience
success = success&& load["aud"]?.ToString() == "roberAudience";
//驗證age>20等
int.TryParse(load["age"].ToString(), out int age);
Assert.IsTrue(age > 30);
//其他驗證 jwt的標(biāo)識 jti是否加入黑名單等
return success;
});
Assert.IsTrue(result);
}
如上面,我們可以把jwt中的payload解析出來,然后進行各種復(fù)雜的想要的驗證;
其實,aspnet core中的基于角色,用戶、策略,自定義策略的驗證就相當(dāng)這里的自定義驗證,一下章將詳細說明和對比,這里暫時不講解
看完上面,是不是覺得jwt很簡單就,主要就兩部
- 創(chuàng)建jwt;
- 驗證jwt;
完整代碼如下:
/// <summary>
/// Token上下文,負責(zé)token的創(chuàng)建和驗證
/// </summary>
public class TokenContext
{
/// <summary>
/// 秘鑰,可以從配置文件中獲取
/// </summary>
public static string securityKey = "GQDstclechengroberbojPOXOYg5MbeJ1XT0uFiwDVvVBrk";
/// <summary>
/// 創(chuàng)建jwttoken,源碼自定義
/// </summary>
/// <param name="payLoad"></param>
/// <param name="header"></param>
/// <returns></returns>
public static string CreateToken(Dictionary<string, object> payLoad,int expiresMinute, Dictionary<string, object> header = null)
{
if (header == null)
{
header = new Dictionary<string, object>(new List<KeyValuePair<string, object>>() {
new KeyValuePair<string, object>("alg", "HS256"),
new KeyValuePair<string, object>("typ", "JWT")
});
}
//添加jwt可用時間(應(yīng)該必須要的)
var now = DateTime.UtcNow;
payLoad["nbf"] = ToUnixEpochDate( now);//可用時間起始
payLoad["exp"] = ToUnixEpochDate(now.Add(TimeSpan.FromMinutes(expiresMinute)));//可用時間結(jié)束
var encodedHeader = Base64UrlEncoder.Encode(JsonConvert.SerializeObject(header));
var encodedPayload = Base64UrlEncoder.Encode(JsonConvert.SerializeObject(payLoad));
var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(securityKey));
var encodedSignature = Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(encodedHeader, ".", encodedPayload))));
var encodedJwt = string.Concat(encodedHeader, ".", encodedPayload, ".", encodedSignature);
return encodedJwt;
}
/// <summary>
/// 創(chuàng)建jwtToken,采用微軟內(nèi)部方法,默認使用HS256加密,如果需要其他加密方式,請更改源碼
/// 返回的結(jié)果和CreateToken一樣
/// </summary>
/// <param name="payLoad"></param>
/// <param name="expiresMinute">有效分鐘</param>
/// <returns></returns>
public static string CreateTokenByHandler(Dictionary<string, object> payLoad, int expiresMinute)
{
var now = DateTime.UtcNow;
// Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims.
// You can add other claims here, if you want:
var claims = new List<Claim>();
foreach (var key in payLoad.Keys)
{
var tempClaim = new Claim(key, payLoad[key]?.ToString());
claims.Add(tempClaim);
}
// Create the JWT and write it to a string
var jwt = new JwtSecurityToken(
issuer: null,
audience: null,
claims: claims,
notBefore: now,
expires: now.Add(TimeSpan.FromMinutes(expiresMinute)),
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(securityKey)), SecurityAlgorithms.HmacSha256));
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
return encodedJwt;
}
/// <summary>
/// 驗證身份 驗證簽名的有效性,
/// </summary>
/// <param name="encodeJwt"></param>
/// <param name="validatePayLoad">自定義各類驗證; 是否包含那種申明,或者申明的值, </param>
/// 例如:payLoad["aud"]?.ToString() == "roberAuddience";
/// 例如:驗證是否過期 等
/// <returns></returns>
public static bool Validate(string encodeJwt,Func<Dictionary<string,object>,bool> validatePayLoad)
{
var success = true;
var jwtArr = encodeJwt.Split('.');
var header = JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlEncoder.Decode(jwtArr[0]));
var payLoad = JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlEncoder.Decode(jwtArr[1]));
var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(securityKey));
//首先驗證簽名是否正確(必須的)
success = success && string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1])))));
if (!success)
{
return success;//簽名不正確直接返回
}
//其次驗證是否在有效期內(nèi)(也應(yīng)該必須)
var now = ToUnixEpochDate(DateTime.UtcNow);
success = success && (now >= long.Parse(payLoad["nbf"].ToString()) && now < long.Parse(payLoad["exp"].ToString()));
//再其次 進行自定義的驗證
success = success && validatePayLoad(payLoad);
return success;
}
/// <summary>
/// 獲取jwt中的payLoad
/// </summary>
/// <param name="encodeJwt"></param>
/// <returns></returns>
public static Dictionary<string ,object> GetPayLoad(string encodeJwt)
{
var jwtArr = encodeJwt.Split('.');
var payLoad = JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlEncoder.Decode(jwtArr[1]));
return payLoad;
}
public static long ToUnixEpochDate(DateTime date) =>
(long)Math.Round((date.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds);
}
以上就是jwt的基本內(nèi)容,它確實很簡單,不要被aspnet core中的各種寫法給搞暈了,只要是jwt相關(guān)的驗證都是基于上面這些東西
下一章節(jié)將講述:
- 在aspnet core中,自定義jwt管道驗證;
- 在aspnet core中,自定義策略驗證CommonAuthorizeHandler : AuthorizationHandler<CommonAuthorize>
- 自定義jwt邏輯驗證和原生的角色,用戶,策略,等進行對比
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關(guān)文章
.Net?Core?3.1?Web?API基礎(chǔ)知識詳解(收藏)
這篇文章主要介紹了.Net?Core?3.1?Web?API基礎(chǔ)知識,本文內(nèi)容篇幅有點長,大家耐心閱讀,此文結(jié)合示例代碼給大家講解的非常詳細,需要的朋友可以參考下2022-04-04
ASP.NET?MVC5網(wǎng)站開發(fā)之用戶角色的后臺管理1(七)
這篇文章主要為大家詳細介紹了ASP.NET?MVC5網(wǎng)站開發(fā)之用戶角色的后臺管理,感興趣的小伙伴們可以參考一下2016-08-08
國產(chǎn)化之銀河麒麟安裝.NetCore包管理器方式(步驟詳解)
這篇文章主要介紹了國產(chǎn)化之銀河麒麟安裝.NetCore-包管理器方式,本文給大家分享安裝步驟及安裝命令,對銀河麒麟安裝.NetCore相關(guān)知識感興趣的朋友一起看看吧2022-03-03
自寫一個模仿Dictionary與Foreach的實現(xiàn)及心得總結(jié)
利用閑暇時間自己寫一個類模仿Dictionary實現(xiàn),如果一個類進行foreach的話,該類必須實現(xiàn)IEnumerable,集合要支持foreach方式的遍歷,必須實現(xiàn)IEnumerable接口,感興趣的你可不要錯過了哈2013-02-02
.NET必知的EventCounters性能指標(biāo)監(jiān)視器詳解
這篇文章主要介紹了.NET必知的EventCounters性能指標(biāo)監(jiān)視器,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-11-11
ajax.net +jquery 無刷新三級聯(lián)動的實例代碼
ajax.net +jquery 無刷新三級聯(lián)動的實例代碼,需要的朋友可以參考一下2013-05-05

