ASP.NET Core的JWT的實現(中間件).md


既然選擇了遠方,便只顧風雨兼程 __ HANS許

 

 

引言:挺久沒更新了,之前做了Vue的系列,后面想做做服務端的系列,上下銜接,我們就講講WebApi(網絡應用程序接口),接口免不了用戶認證,所以接下來我們的主題系列文章便是“基於ASP.NET Core的用戶認證”,分為市面上流行的JWT(JSON WebToken)與OAuth2(開放授權)

JWT(JSON Web Token)

  • 什么叫JWT
    JSON Web Token(JWT)是目前最流行的跨域身份驗證解決方案。

    一般來說,互聯網用戶認證是這樣子的。

    1、用戶向服務器發送用戶名和密碼。
    2、服務器驗證通過后,在當前對話(session)里面保存相關數據,比如用戶角色、登錄時間等等。
    3、服務器向用戶返回一個 session_id,寫入用戶的 Cookie。
    4、用戶隨后的每一次請求,都會通過 Cookie,將 session_id 傳回服務器。
    5、服務器收到 session_id,找到前期保存的數據,由此得知用戶的身份。

    服務器需要保存session,做持久化,這種模式沒有分布式架構,無法支持橫向擴展,如果真的要的話就必須采用分布式緩存來進行管理Seesion。那JWT相反,它保存的是在客戶端,每次請求都將JWT代入服務器,進行簽名,權限驗證。JWT由客戶端請求,服務端生成,客戶端保存,服務端驗證。

  • JWT的原理與格式

    1. 原理
      在上面,我們也講過了,簡單的來說,我們將服務器需要驗證我們的條件(賬戶,密碼等等),發給服務器,服務器認證通過,生成一個JSON對象返回給我們,例如下面。當然,為了防止被篡改,所以我們會將對象加密,再次請求服務器,需要將jwt放在請求頭部,傳遞給服務器,來判斷權限等等。

      1. "姓名": "張三"
      2. "角色": "管理員"
      3. "到期時間": "2018年7月1日0點0分" 
    2. 格式

      • 實際格式
      1. eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. 
      2. eyJBIjoiQUFBQSIsIkIiOiJCQkJCIiwiQyI6IkNDQ0MiLCJ1ZXIiOiJ4dWh1YWxlIiwib3BlbmlkIjoiNTE1NjEzMTM1MTYzMjEiLCJmZiI6ImRmc2RzZGZzZGZzZHMiLCJuYmYiOjE1NTIyMTE4NjAsImV4cCI6MTU1MjIxMzY2MH0. 
      3. 16m57YnnIcgIth25dwphQKPYuIq42BVmZV6LIBO7KDg 

      它是一個很長的字符串,中間用點(.)分隔成三個部分。注意,JWT內部是沒有換行的,這里只是為了便於展示,將它寫成了幾行。JWT 的三個部分依次如下。

      • Header(頭部)
      • Payload(負載)
      • Signature(簽名)

      簡單講下,Header描述加密算法與token類型,Payload描述的是實際需要傳遞的數據,如失效時間,簽發人等等,Signature描述的是一段對於前面兩部部分的簽名,當然秘鑰只有服務器才知道。

簡單的介紹下JWT,更多的話,可以這邊文章看看。我們着重講下實現。

ASP.NET Core 的Middleware實現
  1. 創建JWT
    首先我們要先創建token,畢竟這個是最重要的。Core自帶JWT幫助類,所以我們按照幫助類的意思來寫個方法創建token。

    1. public string CreateJsonWebToken(Dictionary<string, string> payLoad) 
    2. if (string.IsNullOrWhiteSpace(setting.SecurityKey)) 
    3. throw new ArgumentNullException("JsonWebTokenSetting.securityKey"
    4. "securityKey為NULL或空字符串。請在\"appsettings.json\"配置\"JsonWebToken\"節點及其子節點\"securityKey\""); 
    5. var now = DateTime.UtcNow; 
    6.  
    7. // Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims. 
    8. // You can add other claims here, if you want: 
    9. var claims = new List<Claim>(); 
    10. foreach (var key in payLoad.Keys) 
    11. var tempClaim = new Claim(key, payLoad[key]?.ToString()); 
    12. claims.Add(tempClaim); 
    13.  
    14. // Create the JWT and write it to a string 
    15. var jwt = new JwtSecurityToken( 
    16. issuer: null
    17. audience: null
    18. claims: claims, 
    19. notBefore: now, 
    20. expires: now.Add(TimeSpan.FromMinutes(setting.ExpiresMinute)), 
    21. signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(setting.SecurityKey)), SecurityAlgorithms.HmacSha256)); 
    22. var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt); 
    23. return encodedJwt; 

    從方法我們看到,我們傳入的是負載這個片段,而失效時間與秘鑰我們是放在了appsettings.json來進行配置的。使用DI來獲取配置文件中的節點值。

  2. 編寫中間件
    我們都知道,中間件是Core的管道模型組成部分,所以我們在中間件做驗證,來判斷每次請求用戶是有有權限是有該WebApi或者其他API。

    1. 中間件
      1. public JwtCustomerAuthorizeMiddleware(RequestDelegate next, IOptions<JsonWebTokenSetting> options, IJsonWebTokenValidate jsonWebTokenValidate, Func<Dictionary<string, string>, JsonWebTokenSetting, bool> validatePayLoad, List<string> anonymousPathList) 
      2. this._next = next; 
      3. this._setting = options.Value; 
      4. this._jsonWebTokenValidate = jsonWebTokenValidate; 
      5. this._validatePayLoad = validatePayLoad; 
      6. this._anonymousPathList = anonymousPathList; 
      7.  
      8. public async Task Invoke(HttpContext context) 
      9. //JsonWebTokenValidate 
      10. //若是路徑可以匿名訪問,直接跳過 
      11. if (_anonymousPathList.Contains(context.Request.Path.Value)) 
      12. //還未驗證 
      13. await _next(context); 
      14. return
      15. var result = context.Request.Headers.TryGetValue("Authorization", out StringValues authStr); 
      16. if (!result || string.IsNullOrEmpty(authStr.ToString())) 
      17. throw new UnauthorizedAccessException("未授權,請傳遞Header頭的Authorization參數。"); 
      18.  
      19. //進行驗證與自定義驗證 
      20. result = _jsonWebTokenValidate.Validate(authStr.ToString().Substring("Bearer ".Length).Trim() 
      21. , _setting, _validatePayLoad); 
      22. if (!result) 
      23. throw new UnauthorizedAccessException("驗證失敗,請查看傳遞的參數是否正確或是否有權限訪問該地址。"); 
      24.  
      25. await _next(context); 

    從代碼來看,anonymousPathList是URL路徑,若是在這個List內的URL,便可直接跳過驗證,
    接着將authStrtoken代入驗證函數,validatePayLoad卻是我們自代入的委托函數,用於服務器自定義驗證。

    1. 驗證
      驗證方法,我只是做了簽名驗證與時間驗證。並沒有定得死死的,讓用戶自由度的去進行驗證。
      1. public bool Validate(string encodeJwt, JsonWebTokenSetting setting, Func<Dictionary<string, string>, JsonWebTokenSetting, bool> validatePayLoad) 
      2. if (string.IsNullOrWhiteSpace(setting.SecurityKey)) 
      3. throw new ArgumentNullException("JsonWebTokenSetting.securityKey"
      4. "securityKey為NULL或空字符串。請在\"appsettings.json\"配置\"JsonWebToken\"節點及其子節點\"securityKey\""); 
      5.  
      6. var success = true
      7. var jwtArr = encodeJwt.Split('.'); 
      8. var header = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[0])); 
      9. var payLoad = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[1])); 
      10.  
      11. var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(setting.SecurityKey)); 
      12. //首先驗證簽名是否正確(必須的) 
      13. success = success && string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1]))))); 
      14. if (!success) 
      15. return success;//簽名不正確直接返回 
      16. //其次驗證是否在有效期內(也應該必須) 
      17. var now = ToUnixEpochDate(DateTime.UtcNow); 
      18. success = success && (now >= long.Parse(payLoad["nbf"].ToString()) && now < long.Parse(payLoad["exp"].ToString())); 
      19.  
      20. //再其次 進行自定義的驗證 
      21. success = success && validatePayLoad(payLoad, setting); 
      22.  
      23. return success; 
  3. 加載中間件

    1. 使用擴展方法,來封裝中間件
    1. public static IApplicationBuilder UseJwtCustomerAuthorize(this IApplicationBuilder app, Action<IJwtCustomerAuthorezeOption> action) 
    2. var _JwtCustomerAuthorezeOption = app.ApplicationServices.GetService<IJwtCustomerAuthorezeOption>() as JwtCustomerAuthorezeOption; //new JwtCustomerAuthorezeOption(); 
    3. action(_JwtCustomerAuthorezeOption); 
    4. return app.UseMiddleware<JwtCustomerAuthorizeMiddleware>(_JwtCustomerAuthorezeOption.validatePayLoad, _JwtCustomerAuthorezeOption.anonymousPath); 
    1. Startup.cs使用
    • 注冊服務
      1. public void ConfigureServices(IServiceCollection services)
      2. services.AddJwt(Configuration);} 
    • 使用中間件
      1. public void Configure(IApplicationBuilder app, IHostingEnvironment env) 
      2. app.UseJwtCustomerAuthorize(option => 
      3. //設置不會被驗證的url,可以使用鏈式調用一直添加 
      4. option.SetAnonymousPaths(new System.Collections.Generic.List<string>() 
      5. // "/", 
      6. "/Home/Privacy"
      7. "/Home/CreateJsonToken" 
      8. }); 
      9. // 自定義驗證函數,playLoad為帶過來的參數字典,setting為失效時間與秘鑰 
      10. option.SetValidateFunc((playLoad, sertting) => 
      11. return true
      12. }); 
      13. }); 
      14. }  

總結下,通過上面,就完成了JWT在ASP.NET Core使用中間件的方式的實現。簡單來說就是用自帶方法創建token,驗證則使用中間件的形式,每次請求都需要進行驗證當然你可以設置特殊URL。在下篇文章我們來講講使用策略模式的JWT實現。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM