asp net core 2.1中如何使用jwt(從原理到精通)

 更新時間:2018年11月01日 11:49:49   作者:roberg   我要評論

這篇文章主要給大家介紹了關于asp net core 2.1中如何使用jwt(從原理到精通)的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面來一起看看吧

為什么使用 Jwt

最近,移動開發的勁頭越來越足,學校搞的各種比賽都需要用手機 APP 來撐場面,所以,作為寫后端的,很有必要改進一下以往的基于 Session 的身份認證方式了,理由如下:

  • 移動端經常要保持長時間(1 到 2 星期)在線,但是 Session 卻不好在服務端保存這么久,雖然可以持久化到數據庫,但是還是挺費資源
  • 移動端往往不是使用的網頁技術,所以藏在 Cookie 里面的 SessionId 不是很方便的傳遞給服務端
  • 服務端暴露給客戶端的接口往往是 RESTful 風格的,這是一種無狀態的 API 風格,所以身份認證的方式最好也是無狀態的才好

所以我選擇了使用 Jwt (Json Web Token) 這個技術。Jwt 是一種無狀態的分布式的身份驗證方式,與 Session 相反,Jwt 將用戶信息存放在 Token 的 payload 字段保存在客戶端,通過 RSA 加密的方式,保證數據不會被篡改,驗證數據有效性。

下面是一個使用 Jwt 的系統的身份驗證流程:

可以看出,用戶的信息保存在 Token 中,而 Token 分布在用戶的設備中,所以服務端不再需要在內存中保存用戶信息了
身份認證的 Token 傳遞時以一種相當簡單的格式保存在 header 中,方便客戶端對其進行操作

原理

jwt對所有語言都是通用的,只要知道秘鑰,另一一種語言有可以對jwt的有效性進行判斷;

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

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

使用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);

該方法很簡單,只需要傳入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.7Is2KYHAtSr5fW2gPU1jGeHPzz2ULCZJGcWb40LSYyw

第一部分和第二部分,并不是加密,只是Base64轉換,我們可以通過其他語言輕松轉換回來,如下使用javascript進行轉,window.atob(base64加密) window.btoa(base64解密)

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

它效果和上面一模一樣,如果使用同樣的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作為參數傳入進去

我們驗證通過分為兩部分,

第一,必須的(自認為的) 

  • jwt簽名是否正確,請看以上代碼實現 
  • jwt是否在可以時間內,請看以上代碼實現

第二,自定義的(各復雜的,原理就是獲取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的標識 jti是否加入黑名單等

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

如上面,我們可以把jwt中的payload解析出來,然后進行各種復雜的想要的驗證;

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

看完上面,是不是覺得jwt很簡單就,主要就兩部

  • 創建jwt;
  • 驗證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相關的驗證都是基于上面這些東西

下一章節將講述:

  • 在aspnet core中,自定義jwt管道驗證;
  • 在aspnet core中,自定義策略驗證CommonAuthorizeHandler : AuthorizationHandler<CommonAuthorize>
  • 自定義jwt邏輯驗證和原生的角色,用戶,策略,等進行對比

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。

相關文章

  • asp.net判斷字符串是否是中文的方法

    asp.net判斷字符串是否是中文的方法

    asp.net判斷字符串是否是中文的方法,需要的朋友可以參考一下
    2013-03-03
  • 在ASP.NET Core中顯示自定義的錯誤頁面

    在ASP.NET Core中顯示自定義的錯誤頁面

    大家在用瀏覽器訪問服務器時,不同情況下會返回不同的信息。服務器發生錯誤就會返回錯誤信息,我們最熟悉的就是404錯誤頁面,但是這里我想和大家分享下在ASP.NET Core中如何顯示自定義的500或404錯誤頁面,有需要的朋友們可以參考借鑒,下面來一起看看吧。
    2016-12-12
  • c# datatable用法總結

    c# datatable用法總結

    在項目中經常用到DataTable,如果DataTable使用得當,不僅能使程序簡潔實用,而且能夠提高性能,達到事半功倍的效果,現對DataTable的使用技巧進行一下總結。
    2010-09-09
  • asp.net中將js的返回值賦給asp.net控件的小例子

    asp.net中將js的返回值賦給asp.net控件的小例子

    要做一個顯示用戶在線停留時間的功能,拖了一個label控件用于顯示時間,而時間是通過js來實現的,現在要把js的返回值賦給label,方法如下:
    2013-03-03
  • asp.net微軟圖表控件使用示例代碼分享

    asp.net微軟圖表控件使用示例代碼分享

    這篇文章主要介紹了asp.net微軟圖表控件使用示例代碼,有需要的朋友可以參考一下
    2013-12-12
  • asp.net下UTF-7轉GB2312編碼的代碼(中文)

    asp.net下UTF-7轉GB2312編碼的代碼(中文)

    UTF-7轉換GB2312編碼的方法
    2010-07-07
  • C# 事件的設計與使用深入理解

    C# 事件的設計與使用深入理解

    事件是用于通知其他對象發生了本對象發生了特定的事情的類型成員;事件是.NET類型成員中相對較為難以理解和實踐的一個成員,因為事件的定義不是繼承自基礎的數據類型,而是對委托(delegate)的封裝。所以,在了解事件之前,你需要先了解一點委托
    2012-12-12
  • asp.net自定義控件回發數據實現方案與代碼

    asp.net自定義控件回發數據實現方案與代碼

    在實現asp.net的自定義控件中,若要實現數據的回發或者post數據,那自義控件必須實現IPostBackDataHandler接口, 在該接口中有兩個方法一個是LoadPostData,另一個是RaisePostDataChangedEvent,需要的朋友可以了解下
    2012-12-12
  • ASP.NET 圖片加水印防盜鏈實現代碼

    ASP.NET 圖片加水印防盜鏈實現代碼

    ASP.NET 圖片加水印防盜鏈實現代碼,需要的朋友可以參考下。
    2011-12-12
  • .NET橋接模式講解

    .NET橋接模式講解

    這篇文章主要為大家詳細介紹了ASP.NET橋接模式的相關資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-11-11

最新評論

时时彩包赢公式0369