aspnet core 2.1中使用jwt從原理到精通一

 

目錄javascript

  1. 原理;
  2. 根據原理使用C#語言,生成jwt;
  3. 自定義驗證jwt;
  4. 使用aspnetcore 中自帶的類生成jwt;

學有所得java

  1. 瞭解jwt原理;
  2. 使用C#輕鬆實現jwt生成和驗證

原理

jwt對全部語言都是通用的,只要知道祕鑰,另外一一種語言有能夠對jwt的有效性進行判斷;asp.net

jwt的組成;Header部分Base64轉化.Payload部分Base64轉化.使用HS256方式根據祕鑰對前面兩部分進行加密後再Base64轉化,其中使用的hs256加密是header部分指定的,也能夠經過官網的查看,以下圖:dom

原理就這麼簡單,那究竟用怎樣使用C#來實現呢,又怎麼肯定它的正確性呢?,請繼續ide

使用C#實現

咱們定義一個今天方法,其中須要使用到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中存放重要內容,好比密碼等

 

使用aspnetcore 中自帶的類生成jwt

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;
        }
View Code

它效果和上面如出一轍,若是使用一樣的header 、payload、祕鑰,生成的jwt確定同樣,這裏就不演示了,感興趣的能夠自行嘗試;

aspnetcore中如何使用自定義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做爲參數傳入進去

咱們驗證經過分爲兩部分,

  1. 第一,必須的(自認爲的)
    1.  jwt簽名是否正確,請看以上代碼實現
    2.  jwt是否在能夠時間內,請看以上代碼實現
  2. 第二,自定義的(各複雜的,原理就是獲取payLoad 的某個值,而後對這個值進行各類判讀--等於,大於,包含,)
    1.   該jwt是否是進入黑名單
    2.  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的標識 jti是否加入黑名單等

                return success;
            });
            Assert.IsTrue(result);
        }

如上面,咱們能夠把jwt中的payload解析出來,而後進行各類複雜的想要的驗證;

其實,aspnet core中的基於角色,用戶、策略,自定義策略的驗證就至關這裏的自定義驗證,一下章將詳細說明和對比,這裏暫時不講解

看完上面,是否是以爲jwt很簡單就,主要就兩部

  1. 建立jwt;
  2. 驗證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);
    }
View Code

以上就是jwt的基本內容,它確實很簡單,不要被aspnet core中的各類寫法給搞暈了,只要是jwt相關的驗證都是基於上面這些東西

下一章節將講述:

  1. 在aspnet core中,自定義jwt管道驗證;
  2. 在aspnet core中,自定義策略驗證CommonAuthorizeHandler : AuthorizationHandler<CommonAuthorize>
  3. 自定義jwt邏輯驗證和原生的角色,用戶,策略,等進行對比
相關文章
相關標籤/搜索