您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關(guān)asp net core 2.1中使用jwt的方法的內(nèi)容。小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過(guò)來(lái)看看吧。
為什么使用 Jwt
最近,移動(dòng)開(kāi)發(fā)的勁頭越來(lái)越足,學(xué)校搞的各種比賽都需要用手機(jī) APP 來(lái)?yè)螆?chǎng)面,所以,作為寫(xiě)后端的,很有必要改進(jìn)一下以往的基于 Session 的身份認(rèn)證方式了,理由如下:
移動(dòng)端經(jīng)常要保持長(zhǎng)時(shí)間(1 到 2 星期)在線,但是 Session 卻不好在服務(wù)端保存這么久,雖然可以持久化到數(shù)據(jù)庫(kù),但是還是挺費(fèi)資源
移動(dòng)端往往不是使用的網(wǎng)頁(yè)技術(shù),所以藏在 Cookie 里面的 SessionId 不是很方便的傳遞給服務(wù)端
服務(wù)端暴露給客戶端的接口往往是 RESTful 風(fēng)格的,這是一種無(wú)狀態(tài)的 API 風(fēng)格,所以身份認(rèn)證的方式最好也是無(wú)狀態(tài)的才好
所以我選擇了使用 Jwt (Json Web Token) 這個(gè)技術(shù)。Jwt 是一種無(wú)狀態(tài)的分布式的身份驗(yàn)證方式,與 Session 相反,Jwt 將用戶信息存放在 Token 的 payload 字段保存在客戶端,通過(guò) RSA 加密的方式,保證數(shù)據(jù)不會(huì)被篡改,驗(yàn)證數(shù)據(jù)有效性。
下面是一個(gè)使用 Jwt 的系統(tǒng)的身份驗(yàn)證流程:
可以看出,用戶的信息保存在 Token 中,而 Token 分布在用戶的設(shè)備中,所以服務(wù)端不再需要在內(nèi)存中保存用戶信息了
身份認(rèn)證的 Token 傳遞時(shí)以一種相當(dāng)簡(jiǎn)單的格式保存在 header 中,方便客戶端對(duì)其進(jìn)行操作
原理
jwt對(duì)所有語(yǔ)言都是通用的,只要知道秘鑰,另一一種語(yǔ)言有可以對(duì)jwt的有效性進(jìn)行判斷;
jwt的組成;Header部分Base64轉(zhuǎn)化.Payload部分Base64轉(zhuǎn)化.使用HS256方式根據(jù)秘鑰對(duì)前面兩部分進(jìn)行加密后再Base64轉(zhuǎn)化,其中使用的hs256加密是header部分指定的,也可以通過(guò)官網(wǎng)的查看,如下圖:
原理就這么簡(jiǎn)單,那究竟用怎樣使用C#來(lái)實(shí)現(xiàn)呢,又怎么確定它的正確性呢?,請(qǐng)繼續(xù)
使用C#實(shí)現(xiàn)
我們定義一個(gè)今天方法,其中需要使用到Microsoft.IdentityModel.Tokens.dll,asp.net core 2.1再帶,如果其他版本,沒(méi)有自帶,需要nuget 一下這個(gè)類庫(kù)
/// <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可用時(shí)間(應(yīng)該必須要的) var now = DateTime.UtcNow; payLoad["nbf"] = ToUnixEpochDate( now);//可用時(shí)間起始 payLoad["exp"] = ToUnixEpochDate(now.Add(TimeSpan.FromMinutes(expiresMinute)));//可用時(shí)間結(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);
該方法很簡(jiǎn)單,只需要傳入header鍵值對(duì)和payLoad鍵值對(duì),然后根據(jù)原理進(jìn)行Base64轉(zhuǎn)換和hs256加密,接下來(lái)我們來(lái)使用一個(gè)測(cè)試類對(duì)其進(jìn)行測(cè)試,代碼如下:
[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); }
先不管后面的驗(yàn)證,我們先看看其中生成的encodeJwt的值:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJyb2JlciIsImp0aSI6IjY0OWMyYjUxLTE4ZGQtNDEzYy05Yzc5LTI4NWNhMDAxODU2NSIsIm5iZiI6MTU0MDYxMDY2NSwiZXhwIjoxNTQwNjEyNDY1LCJpc3MiOiJyb2Jlcklzc3VlciIsImF1ZCI6InJvYmVyQXVkaWVuY2UiLCJhZ2UiOjMwfQ.7Is2KYHAtSr5fW2gPU1jGeHPzz2ULCZJGcWb40LSYyw
第一部分和第二部分,并不是加密,只是Base64轉(zhuǎn)換,我們可以通過(guò)其他語(yǔ)言輕松轉(zhuǎn)換回來(lái),如下使用javascript進(jìn)行轉(zhuǎn),window.atob(base64加密) window.btoa(base64解密)
var header=JSON.parse(window.atob('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'))
如下圖:
我再對(duì)payLoa進(jìn)行轉(zhuǎn)換回來(lái), var payLoad=JSON.parse(window.atob('eyJzdWIiOiJyb2JlciIsImp0aSI6IjY0OWMyYjUxLTE4ZGQtNDEzYy05Yzc5LTI4NWNhMDAxODU2NSIsIm5iZiI6MTU0MDYxMDY2NSwiZXhwIjoxNTQwNjEyNDY1LCJpc3MiOiJyb2Jlcklzc3VlciIsImF1ZCI6InJvYmVyQXVkaWVuY2UiLCJhZ2UiOjMwfQ'))
,如下圖:
所以,從這里可以看出來(lái),Base64并不是屬于加密,只是簡(jiǎn)單轉(zhuǎn)換,因此,不能在payLoad中存放重要內(nèi)容,比如密碼等
使用aspnetcore 中自帶的類生成jwt
aspnet core中自帶了一個(gè)jwt幫助類,其實(shí)原理一樣,對(duì)上面做了封裝,豐富了一個(gè)內(nèi)容,我們繼續(xù)使用一個(gè)靜態(tài)方法,如下
/// <summary> /// 創(chuàng)建jwtToken,采用微軟內(nèi)部方法,默認(rèn)使用HS256加密,如果需要其他加密方式,請(qǐng)更改源碼 /// 返回的結(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驗(yàn)證
上面講了那么多,只是為了大家更好的理解如何使用jwt進(jìn)行驗(yàn)證,那是jwt是如何進(jìn)行驗(yàn)證的呢?,如果一個(gè)http請(qǐng)求過(guò)來(lái),一般jwt攜帶在http請(qǐng)求頭部的Authorization中;先不看如何獲取,先看看他是如何驗(yàn)證的,我們?cè)俣x個(gè)靜態(tài)方法,如下:
/// <summary> /// 驗(yàn)證身份 驗(yàn)證簽名的有效性, /// </summary> /// <param name="encodeJwt"></param> /// <param name="validatePayLoad">自定義各類驗(yàn)證; 是否包含那種申明,或者申明的值, </param> /// 例如:payLoad["aud"]?.ToString() == "roberAuddience"; /// 例如:驗(yàn)證是否過(guò)期 等 /// <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)); //首先驗(yàn)證簽名是否正確(必須的) success = success && string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1]))))); if (!success) { return success;//簽名不正確直接返回 } //其次驗(yàn)證是否在有效期內(nèi)(也應(yīng)該必須) var now = ToUnixEpochDate(DateTime.UtcNow); success = success && (now >= long.Parse(payLoad["nbf"].ToString()) && now < long.Parse(payLoad["exp"].ToString())); //再其次 進(jìn)行自定義的驗(yàn)證 success = success && validatePayLoad(payLoad); return success; }
其中validatePayLoad 參數(shù)是一個(gè)自定義的驗(yàn)證的Fun,執(zhí)行該Fun方法時(shí)會(huì)把解密后的payload作為參數(shù)傳入進(jìn)去
我們驗(yàn)證通過(guò)分為兩部分,
第一,必須的(自認(rèn)為的)
jwt簽名是否正確,請(qǐng)看以上代碼實(shí)現(xiàn)
jwt是否在可以時(shí)間內(nèi),請(qǐng)看以上代碼實(shí)現(xiàn)
第二,自定義的(各復(fù)雜的,原理就是獲取payLoad 的某個(gè)值,然后對(duì)這個(gè)值進(jìn)行各種判讀--等于,大于,包含,)
該jwt是不是進(jìn)入黑名單
aud==‘roberAudience'
我們來(lái)通過(guò)一個(gè)測(cè)試類驗(yàn)證
[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; //驗(yàn)證是否包含aud 并等于 roberAudience success = success&& load["aud"]?.ToString() == "roberAudience"; //驗(yàn)證age>20等 int.TryParse(load["age"].ToString(), out int age); Assert.IsTrue(age > 30); //其他驗(yàn)證 jwt的標(biāo)識(shí) jti是否加入黑名單等 return success; }); Assert.IsTrue(result); }
如上面,我們可以把jwt中的payload解析出來(lái),然后進(jìn)行各種復(fù)雜的想要的驗(yàn)證;
其實(shí),aspnet core中的基于角色,用戶、策略,自定義策略的驗(yàn)證就相當(dāng)這里的自定義驗(yàn)證,一下章將詳細(xì)說(shuō)明和對(duì)比,這里暫時(shí)不講解
看完上面,是不是覺(jué)得jwt很簡(jiǎn)單就,主要就兩部
創(chuàng)建jwt;
驗(yàn)證jwt;
完整代碼如下:
/// <summary> /// Token上下文,負(fù)責(zé)token的創(chuàng)建和驗(yàn)證 /// </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可用時(shí)間(應(yīng)該必須要的) var now = DateTime.UtcNow; payLoad["nbf"] = ToUnixEpochDate( now);//可用時(shí)間起始 payLoad["exp"] = ToUnixEpochDate(now.Add(TimeSpan.FromMinutes(expiresMinute)));//可用時(shí)間結(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)部方法,默認(rèn)使用HS256加密,如果需要其他加密方式,請(qǐng)更改源碼 /// 返回的結(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> /// 驗(yàn)證身份 驗(yàn)證簽名的有效性, /// </summary> /// <param name="encodeJwt"></param> /// <param name="validatePayLoad">自定義各類驗(yàn)證; 是否包含那種申明,或者申明的值, </param> /// 例如:payLoad["aud"]?.ToString() == "roberAuddience"; /// 例如:驗(yàn)證是否過(guò)期 等 /// <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)); //首先驗(yàn)證簽名是否正確(必須的) success = success && string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1]))))); if (!success) { return success;//簽名不正確直接返回 } //其次驗(yàn)證是否在有效期內(nèi)(也應(yīng)該必須) var now = ToUnixEpochDate(DateTime.UtcNow); success = success && (now >= long.Parse(payLoad["nbf"].ToString()) && now < long.Parse(payLoad["exp"].ToString())); //再其次 進(jìn)行自定義的驗(yàn)證 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); }
感謝各位的閱讀!關(guān)于“asp net core 2.1中使用jwt的方法”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。