從壹開始前后端分離[.netCore ] 36 ║用官方授權中間件實現令牌過期


緣起

哈嘍,老張的不定期更新的日常又開始了,在咱們的前后端分離的.net core 框架中,雖然已經實現了權限驗證《框架之五 || Swagger的使用 3.3 JWT權限驗證【修改】》,只不過還是有一些遺留問題,最近有不少的小伙伴發現了這樣的一些問題,本來想着直接就在原文修改,但是發現可能怕有的小伙伴看不到,就單發一條推送吧,所以我還是單寫出一篇文章來說明解決這些問題,希望對無論是正在開發權限管理系統,還是平時需要數據庫動態綁定權限分配的你有一些啟發和思考。今天咱們注意解決這三個問題:

1、過期時間無效;

2、權限策略是寫死的,如何存入數據庫;

3、如何進行無狀態權限驗證;

之前我也是考慮了一些時間,但是都不是很好的方法,就一直擱淺,正好群里一個大神提供了很好的方法,今天我就不敢用完美來形容了,怕有人批評,嘩眾取寵,因為是上一個系列,而且也是老問題,這里就不過多的進行文字介紹了,直接上代碼。

投稿作者:這里重點說明下,是參考QQ群里小伙伴 Demon @忐-忑 的相關內容,基本都是他的功勞,我只是一個搬運工😀。

 

關於JWT一共三篇 姊妹篇,內容分別從簡單到復雜,一定要多看多想:

      一、Swagger的使用 3.3 JWT權限驗證【修改】

      二、解決JWT權限驗證過期問題

      三、JWT完美實現權限與接口的動態分配

 預告: 關於復雜的詳細的權限驗證系列,我會在DDD領域驅動設計之后,開啟這個基於微服務的 IdentityServer4 系列講解,這里先預告一下。

 

一、解決過期問題

在之前的代碼里,JWT 雖然已經可以實現驗證了,但是卻無法達到過期時間,這個也是一個不大不小的問題,以前之所以無法實現這個功能,主要是犯了兩個小錯誤

1、沒有真正用到JWT的Bearer驗證;

2、使用了自定義的授權,而沒有用官方UseAuthentication授權,導致過期時間沒有生效;

這里就調整下代碼:

 

1、重新設計 IssueJWT 生成 Token 的方法

  /// <summary>
        /// 頒發JWT字符串
        /// </summary>
        /// <param name="tokenModel"></param>
        /// <returns></returns>
        public static string IssueJWT(TokenModelJWT tokenModel)
        {
            var dateTime = DateTime.UtcNow;
//var claims = new Claim[] //{ // new Claim(JwtRegisteredClaimNames.Jti,tokenModel.Uid.ToString()),//Id // new Claim("Role", tokenModel.Role),//角色 // new Claim(JwtRegisteredClaimNames.Iat,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"),
                  new Claim (JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddSeconds(10)).ToUnixTimeSeconds()}")
//}; var claims = new Claim[] { //下邊為Claim的默認配置 new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(JwtRegisteredClaimNames.Iat, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"), new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}") , //這個就是過期時間,目前是過期100秒,可自定義,注意JWT有自己的緩沖過期時間 new Claim (JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddSeconds(100)).ToUnixTimeSeconds()}"), new Claim(JwtRegisteredClaimNames.Iss,"Blog.Core"), new Claim(JwtRegisteredClaimNames.Aud,"wr"), //這個Role是官方UseAuthentication要要驗證的Role,我們就不用手動設置Role這個屬性了 new Claim(ClaimTypes.Role,tokenModel.Role), }; //秘鑰 var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtHelper.secretKey)); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var jwt = new JwtSecurityToken( issuer: "Blog.Core", claims: claims, signingCredentials: creds); var jwtHandler = new JwtSecurityTokenHandler(); var encodedJwt = jwtHandler.WriteToken(jwt); return encodedJwt; }

 

主要的修改,就是Claim[]的聲明上,定義了過期時間和Role。

 

2、修改JWT的權限驗證服務

     //認證
            services.AddAuthentication(x =>
            {
                x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
          .AddJwtBearer(o =>
          {
              o.TokenValidationParameters = new TokenValidationParameters
              {
                  ValidateIssuer = true,//是否驗證Issuer
                  ValidateAudience = true,//是否驗證Audience 
                  ValidateIssuerSigningKey = true,//是否驗證IssuerSigningKey 
                  ValidIssuer = "Blog.Core",
                  ValidAudience = "wr",
                  ValidateLifetime = true,//是否驗證超時  當設置exp和nbf時有效 同時啟用ClockSkew 
                  IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(JwtHelper.secretKey)),//從appsettings.json拿到的密鑰Secret,更多內容往下看 //注意這是緩沖過期時間,總的有效時間等於這個時間加上jwt的過期時間,如果不配置,默認是5分鍾
                  ClockSkew = TimeSpan.FromSeconds(30)

              };
          });

 

更新:注意上邊的代碼在Github中已經有了修改,基本內容都一樣,只是位置微調了,說白了,就是把這一塊封裝了一個實例罷了,不會看不懂:

// 2.1【認證】、官方JWT認證
services.AddAuthentication(x =>
 {
     x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
     x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
 })
 .AddJwtBearer(o =>
 {
     o.TokenValidationParameters = tokenValidationParameters;    
 });

//上邊用到的 tokenValidationParameters 
 //讀取配置文件
 var audienceConfig = Configuration.GetSection("Audience");
 var symmetricKeyAsBase64 = audienceConfig["Secret"];
 var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);
 var signingKey = new SymmetricSecurityKey(keyByteArray);

 // 令牌驗證參數
 var tokenValidationParameters = new TokenValidationParameters
 {
     ValidateIssuerSigningKey = true,
     IssuerSigningKey = signingKey,//還是從 appsettings.json 拿到的
     ValidateIssuer = true,
     ValidIssuer = audienceConfig["Issuer"],//發行人
     ValidateAudience = true,
     ValidAudience = audienceConfig["Audience"],//訂閱人
     ValidateLifetime = true,
     ClockSkew = TimeSpan.Zero,
     RequireExpirationTime = true,
 };

 

其實和之前的方法是一樣的,只不過請注意 ClockSkew 屬性,默認是5分鍾緩沖。

總的Token有效時間 = JwtRegisteredClaimNames.Exp + ClockSkew ;

 

3、啟動權限認證配置

在之前的方法中,我們用到了中間件 app.UseMiddleware<JwtTokenAuth>(); 當然也是可以的,只不過授權的時候寫的不全,才導致驗證的時候有效時間沒辦法識別,因為我們在生成Token的時候,已經配置好了 claim 聲明,所以直接調用官方的驗證即可。這樣的好處是,我們也不用去判斷  Headers 是否包含 Authorization 的操作;

 //app.UseMiddleware<JwtTokenAuth>();//注意此授權方法已經放棄,請使用下邊的官方授權方法。這里僅僅是授權方法的替換
 app.UseAuthentication();

雖然這個時候我們放棄了使用中間件來授權,但是通過大家的學習,已經完全掌握了中間件的使用了吧,也算是對中間件的一個學習過程,因為在其他地方繼續使用其他的中間件。

重要: 

這里使用 app.UseAuthentication(); 的目的是為了替換授權方法,如果你仍需要中間件傳值的話,比如把用戶信息寫入全局,請繼續使用中間件!

 

4、重要:正確的Token輸入方法

在之前中,我犯了一個想當然的錯誤,然后就直接是解析的 Token 字符串,獲取到數據,這個自然是沒有錯的,只不過這樣就無法正常的使用認證服務中的 AddJwtBearer 方法。那該怎么辦呢,很簡單,就是以后在 Http請求的時候,帶上Bearer(空格)Token,這樣的格式,比如:Bearer 96sdfoysgoi79d87g.sd0ug97sdgf15fdg4531dfg

5、測試接口,查看是否有效

這個時候我們等待130秒,就可以看到已經過期了,如果你沒有明白為啥是130秒,請看上文

 

二、把驗證策略寫到數據庫

其實之前我已經在數據庫表結構中,配置了用到的數據庫表,只不過一直沒有用, 

 

├── Module                                // 菜單表
├── ModulePermission                        // 菜單與按鈕關系表
├── Permission                              // 按鈕表 
├── Role                                    // 角色表
├── RoleModulePermission                    // 按鈕跟權限關聯表
├── UserRole                                // 用戶跟角色關聯表
└── sysUserInfo                             // 用戶信息表 

 

 目前我采用的是,直接獲取當前用戶的全部角色信息,賦值給 JWT 的Token,然后通過 UseAuthentication() 進行授權

 

//獲取當前用戶全部的角色信息(字符串,逗號隔開)
 var userRoles = await sysUserInfoServices.GetUserRoleNameStr(name, pass);
 if (user != null)
 {

     TokenModelJWT tokenModel = new TokenModelJWT();
     tokenModel.Uid = 1;
     tokenModel.Role = user;
}

 

這里先留下一個坑,以后再開發權限管理系統的時候,再單寫一個系統吧。

 

三、無狀態與有狀態驗證

1、無狀態授權

在第一部分中,我們不僅已經實現了Token的有效期,而且自然而然是實現了授權驗證,只需要在知道的Controller 或者方法上增加特性 [Authorize] 就可以實現驗證

 

過程是這樣的,我們登陸,認證用戶信息,成功后,分發Role,然后生成 Token ,這個時候就已經代表當前用戶是有有權限的,只不過是無狀態的,我們不知道他的具體是什么角色,但是會被 app.UseAuthentication(); 識別並通過,如果我們僅僅想給接口增加一個驗證,而不要求角色信息,就可以這么操作。

如果想在授權的controller中,讓某一個方法可以讓所有人訪問,可以增加  [AllowAnonymous] 特性;

 [HttpGet("{id}")]
 [AllowAnonymous]//不受授權控制,任何人都可訪問
 public ActionResult<string> Get(int id)
 {
     return "value";
 }

 

那這個時候你會問,我如果就想要當前用戶必須是某一個Role才能訪問呢,請往下看。

2、有角色授權

 這個時候我們就需要增加 Role 信息了,比如這樣:

 

注意:在使用 Policy 的時候,以前我寫的有問題,請注意修改 

 

  services.AddAuthorization(options =>
  {
      options.AddPolicy("Client", policy => policy.RequireRole("Client").Build());
      options.AddPolicy("Admin", policy => policy.RequireRole("Admin").Build());
      //這個寫法是錯誤的,這個是並列的關系,不是或的關系
      //options.AddPolicy("AdminOrClient", policy => policy.RequireRole("Admin,Client").Build());
      
      //這個才是的關系
      options.AddPolicy("SystemOrAdmin", policy => policy.RequireRole("Admin", "System"));
  });

 

四、權限管理系統Id4

 這個系列我會在DDD領域驅動設計之后,開啟這個 IdentityServer4 系列講解,這里先預告一下。

系列已經開啟:

從壹開始 [ Id4 ] 之一║ 授權服務器 IdentityServer4 開篇講&計划書

 

 

五、Github & Gitee

https://github.com/anjoy8/Blog.Core

https://gitee.com/laozhangIsPhi/Blog.Core


免責聲明!

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



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