從壹開始先後端分離[.netCore 不按期 ] 36 ║解決JWT自定義中間件受權過時問題

緣起

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

一、過時時間無效;git

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

三、如何進行無狀態權限驗證;數據庫

以前我也是考慮了一些時間,可是都不是很好的方法,就一直擱淺,正好羣裏一個大神提供了很好的方法,今天我就不敢用完美來形容了,怕有人批評,譁衆取寵,由於是上一個系列,並且也是老問題,這裏就不過多的進行文字介紹了,直接上代碼。json

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

 

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

      1、Swagger的使用 3.3 JWT權限驗證【修改】服務器

      2、解決JWT權限驗證過時問題app

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

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

 

1、解決過時問題

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

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

二、使用了自定義的受權,而沒有用官方UseAuthentication受權,致使過時時間沒有生效;

這裏就調整下代碼:

 

一、從新設計 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。

 

二、修改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 ;

 

三、啓動權限認證配置

在以前的方法中,咱們用到了中間件 app.UseMiddleware<JwtTokenAuth>(); 固然也是能夠的,只不過受權的時候寫的不全,才致使驗證的時候有效時間沒辦法識別,由於咱們在生成Token的時候,已經配置好了 claim 聲明,因此直接調用官方的驗證便可。這樣的好處是,咱們也不用去判斷  Headers 是否包含 Authorization 的操做;

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

雖然這個時候咱們放棄了使用中間件來受權,可是經過你們的學習,已經徹底掌握了中間件的使用了吧,也算是對中間件的一個學習過程,由於在其餘地方繼續使用其餘的中間件。

重要: 

這裏使用 app.UseAuthentication(); 的目的是爲了替換受權方法,若是你仍須要中間件傳值的話,好比把用戶信息寫入全局,請繼續使用中間件!

 

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

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

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

這個時候咱們等待130秒,就能夠看到已通過期了,若是你沒有明白爲啥是130秒,請看上文

 

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

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

 

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

 

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

 

3、無狀態與有狀態驗證

一、無狀態受權

在第一部分中,咱們不只已經實現了Token的有效期,並且天然而然是實現了受權驗證,只須要在知道的Controller 或者方法上增長特性 [Authorize] 就能夠實現驗證

 

過程是這樣的,咱們登錄,認證用戶信息,成功後,分發Role,而後生成 Token ,這個時候就已經表明當前用戶是有有權限的,只不過是無狀態的,咱們不知道他的具體是什麼角色,可是會被 app.UseAuthentication(); 識別並經過,若是咱們僅僅想給接口增長一個驗證,而不要求角色信息,就能夠這麼操做。

若是想在受權的controller中,讓某一個方法可讓全部人訪問,能夠增長  [AllowAnonymous] 特性;

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

 

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

二、有角色受權

 這個時候咱們就須要增長 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"));
  });

 

4、權限管理系統Id4

 這個系列我會在DDD領域驅動設計以後,開啓這個 IdentityServer4 系列講解,這裏先預告一下。

系列已經開啓:

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

 

 

5、Github & Gitee

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

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

相關文章
相關標籤/搜索