从壹开始前后端分离[.netCore 不定期 ] 36 ║解决JWT权限验证过期问题

缘起

哈喽,老张的不定期更新的日常又开始了,在咱们的前后端分离的.net core 框架中,虽然已经实现了权限验证《框架之五 || Swagger的使用 3.3 JWT权限验证【修改】》,只不过还是有一些遗留问题,最近有不少的小伙伴发现了这样的一些问题,本来想着直接就在原文修改,但是发现可能怕有的小伙伴看不到,就单发一条推送吧,所以我还是单写出一篇文章来说明解决这些问题,希望对无论是正在开发权限管理系统,还是平时需要数据库动态绑定权限分配的你有一些启发和思考。今天咱们注意解决这三个问题:

1、过期时间无效;

2、权限策略是写死的,如何存入数据库;

3、如何进行无状态权限验证;

之前我也是考虑了一些时间,但是都不是很好的方法,就一直搁浅,正好群里一个大神提供了很好的方法,今天我就不敢用完美来形容了,怕有人批评,哗众取宠,因为是上一个系列,而且也是老问题,这里就不过多的进行文字介绍了,直接上代码。

投稿作者:这里重点说明下,是参考QQ群里小伙伴 Demon @忐-忑 的相关内容,基本都是他的功劳,我只是一个搬运工😀。

预告: 关于复杂的详细的权限验证系列,我会在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)), //注意这是缓冲过期时间,总的有效时间等于这个时间加上jwt的过期时间,如果不配置,默认是5分钟
                  ClockSkew = TimeSpan.FromSeconds(30)

              };
          });

其实和之前的方法是一样的,只不过请注意 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

image
image

5、测试接口,查看是否有效

这个时候我们等待130秒,就可以看到已经过期了,如果你没有明白为啥是130秒,请看上文

image

二、把验证策略写到数据库

其实之前我已经在数据库表结构中,配置了用到的数据库表,只不过一直没有用,

├── 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] 就可以实现验证

image

过程是这样的,我们登陆,认证用户信息,成功后,分发Role,然后生成 Token ,这个时候就已经代表当前用户是有有权限的,只不过是无状态的,我们不知道他的具体是什么角色,但是会被 app.UseAuthentication(); 识别并通过,如果我们仅仅想给接口增加一个验证,而不要求角色信息,就可以这么操作。

如果想在授权的controller中,让某一个方法可以让所有人访问,可以增加 [AllowAnonymous] 特性;

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

那这个时候你会问,我如果就想要当前用户必须是某一个Role才能访问呢,请往下看。

2、有角色授权

这个时候我们就需要增加 Role 信息了,比如这样:

image

注意:在使用 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 系列讲解,这里先预告一下。

五、Github & Gitee

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容