從壹開始前后端分離[.netCore 不定期 ] 36 ║解決JWT權(quán)限驗證過期問題

緣起

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

1、過期時間無效;

2、權(quán)限策略是寫死的,如何存入數(shù)據(jù)庫;

3、如何進(jìn)行無狀態(tài)權(quán)限驗證;

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

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

預(yù)告: 關(guān)于復(fù)雜的詳細(xì)的權(quán)限驗證系列,我會在DDD領(lǐng)域驅(qū)動設(shè)計之后,開啟這個基于微服務(wù)的 IdentityServer4 系列講解,這里先預(yù)告一下。

一、解決過期問題

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

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

2、使用了自定義的授權(quán),而沒有用官方UseAuthentication授權(quán),導(dǎo)致過期時間沒有生效;

這里就調(diào)整下代碼:

1、重新設(shè)計 IssueJWT 生成 Token 的方法

  /// <summary>
        /// 頒發(fā)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的默認(rèn)配置
                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,我們就不用手動設(shè)置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的權(quán)限驗證服務(wù)

     //認(rèn)證
            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,//是否驗證超時  當(dāng)設(shè)置exp和nbf時有效 同時啟用ClockSkew 
                  IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(JwtHelper.secretKey)), //注意這是緩沖過期時間,總的有效時間等于這個時間加上jwt的過期時間,如果不配置,默認(rèn)是5分鐘
                  ClockSkew = TimeSpan.FromSeconds(30)

              };
          });

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

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

3、啟動權(quán)限認(rèn)證配置

在之前的方法中,我們用到了中間件 app.UseMiddleware<JwtTokenAuth>(); 當(dāng)然也是可以的,只不過授權(quán)的時候?qū)懙牟蝗?,才?dǎo)致驗證的時候有效時間沒辦法識別,因為我們在生成Token的時候,已經(jīng)配置好了 claim 聲明,所以直接調(diào)用官方的驗證即可。這樣的好處是,我們也不用去判斷 Headers 是否包含 Authorization 的操作;

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

雖然這個時候我們放棄了使用中間件來授權(quán),但是通過大家的學(xué)習(xí),已經(jīng)完全掌握了中間件的使用了吧,也算是對中間件的一個學(xué)習(xí)過程,因為在其他地方繼續(xù)使用其他的中間件。

重要:

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

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

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

image
image

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

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

image

二、把驗證策略寫到數(shù)據(jù)庫

其實之前我已經(jīng)在數(shù)據(jù)庫表結(jié)構(gòu)中,配置了用到的數(shù)據(jù)庫表,只不過一直沒有用,

├── Module                                // 菜單表
├── ModulePermission                        // 菜單與按鈕關(guān)系表
├── Permission                              // 按鈕表 
├── Role                                    // 角色表
├── RoleModulePermission                    // 按鈕跟權(quán)限關(guān)聯(lián)表
├── UserRole                                // 用戶跟角色關(guān)聯(lián)表
└── sysUserInfo                             // 用戶信息表 

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

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

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

這里先留下一個坑,以后再開發(fā)權(quán)限管理系統(tǒng)的時候,再單寫一個系統(tǒng)吧。

三、無狀態(tài)與有狀態(tài)驗證

1、無狀態(tài)授權(quán)

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

image

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

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

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

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

2、有角色授權(quán)

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

image

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

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

四、權(quán)限管理系統(tǒng)Id4

這個系列我會在DDD領(lǐng)域驅(qū)動設(shè)計之后,開啟這個 IdentityServer4 系列講解,這里先預(yù)告一下。

五、Github & Gitee

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

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容