目錄javascript
學有所得java
jwt對全部語言都是通用的,只要知道祕鑰,另外一一種語言有能夠對jwt的有效性進行判斷;asp.net
jwt的組成;Header部分Base64轉化.Payload部分Base64轉化.使用HS256方式根據祕鑰對前面兩部分進行加密後再Base64轉化,其中使用的hs256加密是header部分指定的,也能夠經過官網的查看,以下圖:dom
原理就這麼簡單,那究竟用怎樣使用C#來實現呢,又怎麼肯定它的正確性呢?,請繼續ide
咱們定義一個今天方法,其中須要使用到Microsoft.IdentityModel.Tokens.dll,asp.net core 2.1再帶,若是其餘版本,沒有自帶,須要nuget 一下這個類庫測試
/// <summary> /// 建立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可用時間(應該必需要的) var now = DateTime.UtcNow; payLoad["nbf"] = ToUnixEpochDate( now);//可用時間起始 payLoad["exp"] = ToUnixEpochDate(now.Add(TimeSpan.FromMinutes(expiresMinute)));//可用時間結束 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);ui
該方法很簡單,只須要傳入header鍵值對和payLoad鍵值對,而後根據原理進行Base64轉換和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.7Is2KYHAtSr5fW2gPU1jGeHPzz2ULCZJGcWb40LSYywspa
第一部分和第二部分,並非加密,只是Base64轉換,咱們能夠經過其餘語言輕鬆轉換回來,以下使用javascript進行轉,window.atob(base64加密) window.btoa(base64解密).net
var header=JSON.parse(window.atob('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'))
以下圖:
我再對payLoa進行轉換回來, var payLoad=JSON.parse(window.atob('eyJzdWIiOiJyb2JlciIsImp0aSI6IjY0OWMyYjUxLTE4ZGQtNDEzYy05Yzc5LTI4NWNhMDAxODU2NSIsIm5iZiI6MTU0MDYxMDY2NSwiZXhwIjoxNTQwNjEyNDY1LCJpc3MiOiJyb2Jlcklzc3VlciIsImF1ZCI6InJvYmVyQXVkaWVuY2UiLCJhZ2UiOjMwfQ')) ,以下圖:
因此,從這裏能夠看出來,Base64並非屬於加密,只是簡單轉換,所以,不能在payLoad中存放重要內容,好比密碼等
aspnet core中自帶了一個jwt幫助類,其實原理同樣,對上面作了封裝,豐富了一個內容,咱們繼續使用一個靜態方法,以下
/// <summary> /// 建立jwtToken,採用微軟內部方法,默認使用HS256加密,若是須要其餘加密方式,請更改源碼 /// 返回的結果和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確定同樣,這裏就不演示了,感興趣的能夠自行嘗試;
上面講了那麼多,只是爲了你們更好的理解如何使用jwt進行驗證,那是jwt是如何進行驗證的呢?,若是一個http請求過來,通常jwt攜帶在http請求頭部的Authorization中;先不看如何獲取,先看看他是如何驗證的,咱們再定義個靜態方法,以下:
/// <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;//簽名不正確直接返回 } //其次驗證是否在有效期內(也應該必須) 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 參數是一個自定義的驗證的Fun,執行該Fun方法時會把解密後的payload做爲參數傳入進去
咱們驗證經過分爲兩部分,
咱們來經過一個測試類驗證
[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的標識 jti是否加入黑名單等 return success; }); Assert.IsTrue(result); }
如上面,咱們能夠把jwt中的payload解析出來,而後進行各類複雜的想要的驗證;
其實,aspnet core中的基於角色,用戶、策略,自定義策略的驗證就至關這裏的自定義驗證,一下章將詳細說明和對比,這裏暫時不講解
看完上面,是否是以爲jwt很簡單就,主要就兩部
完整代碼以下:
/// <summary> /// Token上下文,負責token的建立和驗證 /// </summary> public class TokenContext { /// <summary> /// 祕鑰,能夠從配置文件中獲取 /// </summary> public static string securityKey = "GQDstclechengroberbojPOXOYg5MbeJ1XT0uFiwDVvVBrk"; /// <summary> /// 建立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可用時間(應該必需要的) var now = DateTime.UtcNow; payLoad["nbf"] = ToUnixEpochDate( now);//可用時間起始 payLoad["exp"] = ToUnixEpochDate(now.Add(TimeSpan.FromMinutes(expiresMinute)));//可用時間結束 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> /// 建立jwtToken,採用微軟內部方法,默認使用HS256加密,若是須要其餘加密方式,請更改源碼 /// 返回的結果和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;//簽名不正確直接返回 } //其次驗證是否在有效期內(也應該必須) 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的基本內容,它確實很簡單,不要被aspnet core中的各類寫法給搞暈了,只要是jwt相關的驗證都是基於上面這些東西
下一章節將講述: