ASP.NET Core 实战:基于 Jwt Token 的权限控制全揭露

一、前言

  在涉及到后端项目的开发中,如何实现对于用户权限的管控是需要我们首先考虑的,在实际开发过程中,我们可能会运用一些已经成熟的解决方案帮助我们实现这一功能,而在 Grapefruit.VuCore 这个项目中,我将使用 Jwt 的方式实现对于用户的权限管控,在本章中,我将演示如何使用 Jwt 实现对于用户的授权、鉴权。

  系列目录地址:ASP.NET Core 项目实战
  仓储地址:https://github.com/Lanesra712/Grapefruit.VuCore

 二、Step by Step

  1、一些概念

  Jwt(json web token),是一种基于 Json 的无状态授权令牌,因为 Jwt 是一种标准的数据传输规范,并不是某家所独有的技术规范,因此非常适用于构建单点登录服务,为 web、client、app 等等各种接口使用方提供授权服务。

  在使用 Jwt 进行权限控制的过程中,我们需要先请求授权服务器获取到 token 令牌,将令牌存储到客户端本地(在 web 项目中,我们可以将 token 存储到 localstorage 或是 cookie 中),之后,对于服务端的每一次请求,都需要将获取到的 token 信息添加到 http 请求的 header 中。

640?wx_fmt=png


$.ajax({url: url,method: "POST",data: JSON.stringify(data),beforeSend: function (xhr) {        /* Authorization header */xhr.setRequestHeader("Authorization", "Bearer " + token);},success: function (data) {}
});


  当用户拥有令牌后是否就可以访问系统的所有功能了呢?答案当然否定的。对于一个系统来说可能会有多种用户角色,每一个用户角色可以访问的资源也是不同的,所以,当用户已经拥有令牌后,我们还需要对用户角色进行鉴定,从而做到对用户进行进一步的权限控制。

  在 Grapefruit.VuCore 这个项目中,我采用的是基于策略的授权方式,通过定义一个授权策略来完善 Jwt 鉴权,之后将这个自定义策略注入到 IServiceCollection 容器中,对权限控制做进一步的完善,从而达到对于用户访问权限的管控。

  基于策略的授权是微软在 ASP.NET Core 中添加的一种新的授权方式,通过定义好策略(policy)的一个或多个要求(requirements),将这个自定义的授权策略在 Startup.ConfigureServices 方法中作为授权服务配置的一部分进行注册之后即可按照我们的策略处理程序进行权限的控制。

640?wx_fmt=png

就像我在后面的代码中一样,我定义了一个名叫 Permission 的授权策略,它包含了一个叫做 PolicyRequirement 的鉴权要求,在实现了授权策略后,将基于这个要求的鉴权方法 PolicyHandler 以单例(AddSingleton)的形式注入到服务集合中,此时,我们就可以在需要添加验证的 controller 上添加 attribute 即可。

[Authorize(Policy = "Permission")]
public class SecretController : ControllerBase {}

  2、授权

  在 Grapefruit.VuCore 这个项目中,涉及到授权相关的代码所在的位置我已在下图中进行标示。在之前系列开篇文章(ASP.NET Core 实战:使用 ASP.NET Core Web API 和 Vue.js,搭建前后端分离框架)进行介绍整个项目框架时曾说到, Grapefruit.Application 是项目的应用层,顾名思义,就是为了实现我们项目中的实际业务功能所划分的类库。因此,我们实现 Jwt 的相关业务代码应该位于此层中。同时,因为对于 Jwt 的令牌颁发与鉴权,采用的是微软的 JwtBearer 组件,所以我们在使用前需要先通过 Nuget 将引用添加到 Grapefruit.Application 上。

Install-Package Microsoft.AspNetCore.Authentication.JwtBearer
Install-Package System.IdentityModel.Tokens.Jwt

640?wx_fmt=png

  在 Grapefruit.Application 这个类库下我创建了一个  Authorization 解决方案文件夹用来存储授权相关的代码。在 Authorization 这个解决方案文件夹中包含了两个子文件夹 Jwt 和 Secret。Jwt 文件夹中主要包含我们对于 Jwt 的操作,而 Secret 文件夹下则是对于用户的相关操作。

  每个子应用文件夹(Jwt、Secret)都包含了相同的结构:Dto 数据传输对象、功能接口,以及功能接口的实现类,这里接口的继承采用单继承的方式。

640?wx_fmt=png

  在 Jwt 文件夹下创建一个 IJwtAppService 接口文件,在这里定义我们对于 Jwt 的相关操作。因为对于 Jwt 的授权、鉴权是采用微软的 JwtBearer 组件,我们只需要进行配置即可,所以这里只定义对于 token 的生成、刷新、停用,以及判断这个 token 是否有效这几个方法。同时,我们需要创建 JwtAppService 这个类文件,去继承 IJwtAppService 从而实现接口功能。

640?wx_fmt=png

  JwtAuthorizationDto 是一个 token 信息的传输对象,包含我们创建好的 token 相关信息,用来将 token 信息返回给前台进行使用。而 UserDto 则是用户登录获取 token 时的数据传输对象,用来接收登录时的参数值。

  在创建 token 或是验证 token 时,像 token 的颁发者、接收者之类的信息,因为会存在多个地方调用的可能性,这里我将这些信息存放在了配置文件中,后面当我们需要使用的时候,只需要通过注入 IConfiguration 进行获取即可。关于 Jwt 的配置文件主要包含了四项:token 的颁发者,token 的接收者,加密 token 的 key 值,以及 token 的过期时间,你可以根据你自己的需求进行调整。

"Jwt": {    "Issuer": "yuiter.com",    "Audience": "yuiter.com",    "SecurityKey": "a48fafeefd334237c2ca207e842afe0b",    "ExpireMinutes": "20"}

  在 token 的创建过程中可以简单拆分为三个部分:根据配置信息和用户信息创建一个 token,将加密后的用户信息写入到 HttpContext 上下文中,以及将创建好的 token 信息添加到静态的 HashSet<JwtAuthorizationDto> 集合中。

  在 token 创建、校验的整个生命周期中,都涉及到了  Scheme、Claim、ClaimsIdentity、ClaimsPrincipal 这些概念,如果你之前有使用过微软的 Identity 权限验证,对于这几个名词就会比较熟悉,可能某些小伙伴之前并没有使用过 Identity,我来简单介绍下这几个名词的含义。

  Scheme 模式,这个与其余的名词相对独立,它主要是指明我们是以什么授权方式进行授权的。例如,你是以 cookie 的方式授权或是以 OpenId 的方式授权,或是像这里我们使用 Jwt Bearer 的方式进行授权。

  Claim 声明,以我们的现实生活为例,我们每个人都会有身份证,上面会包含我们的姓名、性别、民族、出生日期、家庭住址、身份证号,每一项数据的都可以看成是 type-value(数据类型-数据值),例如,姓名:张三。身份证上的每一项的信息就是我们的 Claim 声明,姓名:张三,是一个 Claim;性别:男,也是一个 Claim。而对于 ClaimsIdentity,就像这一项项的信息最终组成了我们的身份证,这一项项的 Claim 最终组成了我们的 ClaimsIdentity。而 ClaimsPrincipal 则是 ClaimsIdentity 的持有者,就像我们拥有身份证一样。

  从上面的文字可以总结出,Claim(每一项的证件信息)=》ClaimsIdentity(证件)=》ClaimsPrincipal(证件持有者)。其中,一个 ClaimsIdentity 可以包含多个的 Claim,而一个 ClaimsPrincipal 可以包含多个的 ClaimsIdentity。

   如果想要深入了解 ASP.NET Core 的授权策略的可以看看园子里这篇文章 =》ASP.NET Core 运行原理解剖[5]:Authentication,或是国外的这篇介绍 ASP.NET Core 授权的文章 =》Introduction to Authentication with ASP.NET Core。

  实现 token 生成的最终代码实现如下所示,可以看到,在创建 ClaimsIdentity “证件”信息时,我添加了用户的角色信息,并把加密后的用户信息写入到 HttpContext 上下文中,这样,我们在后面验证的时候就可以通过 HttpContext 获取到用户的角色信息,从而判断用户是否可以访问当前请求的地址。

640?wx_fmt=png

  当创建好 token 之后,客户端就可以在 Http 请求的 header 中添加 token 信息,从而访问受保护的资源。不过,在某些情况下,比如说,用户修改了密码之后,虽然当前的 token 信息可能还未过期,但我们也不能允许用户再使用当前的 token 信息进行接口的访问,这时,就涉及到了对于 token 信息的停用以及刷新。

  这里我采用是当我们停用 token 信息时,将停用的 token 信息添加到 Redis 缓存中,之后,在用户请求时判断这个 token 是不是存在于 Redis 中即可。

  当然,你也可以在停用当前用户的 token 信息时,将 HashSet 中的这个 token 信息进行删除,之后,通过判断访问时的 token 信息是否在 HashSet 集合中,判断 token 是否有效。

  方法很多,看你自己的需求了。

  对于 Redis 的读写操作,我是使用微软的 Redis 组件进行的,你可以按照你的喜好进行修改。如果你和我一样,采用这个组件,你需要在 Grapefruit.Application 这个类库中通过 Nuget 添加微软的分布式缓存抽象接口 dll 的引用,以及在 Grapefruit.WebApi 项目中添加微软的 Redis 实现。

Install-Package Microsoft.Extensions.Caching.Abstractions 
## 分布式缓存抽象接口
Install-Package Microsoft.Extensions.Caching.Redis
## Redis 实现

  当我们停用 token 时,通过 HttpContext 上下文获取到 HTTP Header 中的 token 信息,将该 token 信息存储到 Redis 缓存中,这样,我们就完成了对于 token 的停用。

640?wx_fmt=png

  对于 token 的刷新,其实我们就可以看成重新生成了一个 token 信息,只不过我们需要将之前的 token 信息进行停用。

640?wx_fmt=png

  至此,我们对于 token 的创建、刷新、停用的代码就已经完成了,接下来,我们来实现对于 token 信息的验证。PS:下面的代码如无特殊说明外,均位于 Startup 类中。

  3、鉴权

  在 ASP.NET Core 应用中,依赖注入随处可见,而我们对于我们的功能方法的使用,也是采用依赖注入到容器,通过功能接口进行调用的方式。因此,我们需要将我们的接口与其实现类注入到 IServiceCollection 容器中。这里,我们采用反射的方式,批量的将程序集内的接口与其实现类进行注入。

640?wx_fmt=png

 因为基础的权限验证我们是采用的微软的 JwtBearer 权限验证组件进行的授权和鉴权,因此对于 token 信息的基础鉴权操作,只需要我们在中间件中进行配置即可。同时,我们也在 IJwtAppService 接口中定义了对于 token 信息的一些操作,而对于我们自定义的权限验证策略,则需要通过基于策略的授权方式进行实现。

  首先,我们需要先定义一个继承于 IAuthorizationRequirement 的自定义授权要求类 PolicyRequirement。在这个类中,你可以定义一些属性,通过有参构造函数的方式进行构造,这里我不定义任何的属性,仅是创建这个类。

public class PolicyRequirement : IAuthorizationRequirement
{ }

  当我们创建好 PolicyRequirement 这个权限要求类后,我们就可以通过继承 AuthorizationHandler 来实现我们的授权逻辑。这里实现权限控制的代码逻辑,主要是通过重写 HandleRequirementAsync 方法来实现的。

640?wx_fmt=png

  在判断用户是否可以访问当前的请求地址时,首先需要获取到用户角色与其允许访问的地址列表,这里我使用的是模拟的数据。通过判断当前登录用户的角色是否包含请求的地址,当用户的角色并不包含对于访问地址的权限时,返回 403 Forbidden 状态码。

  这里需要注意,如果你准备采取 RESTful 风格的 API,因为请求的地址是相同的,你需要添加一个 HTTP 谓词参数用来指明所请求的方法,从而达到访问权限管控的目的。。

  包含 token 的基础验证的授权配置的代码如下所示。在中间件进行 Jwt 验证的过程中,会验证授权方式是不是 Bearer 以及通过 token 的属性解密之后与生成时用户数据进行比对,从而判断这个 token 是否有效。

640?wx_fmt=png

640?wx_fmt=png

  因为我们是使用 Swagger 进行的 API 文档的可视化,这里,我们继续配置 Swagger 从而使 Swagger 可以支持我们的权限验证方式。

640?wx_fmt=png

  在停用 token 的代码中,我们使用了 Redis 去保存停用的 token 信息,因此,我们需要配置我们的 Redis 连接。

640?wx_fmt=png

  现在,整个业务相关的代码已经完成了,我们可以创建前端访问的接口了。这里我是在 Controllers 下的 V1 文件夹下创建了一个 SecretController 用来构建前端访问的接口。控制器中主要有三个方法,分别为 CancelAccessToken(停用 token)、Login(获取 token)以及 RefreshAccessTokenAsync(刷新 token)。

640?wx_fmt=png

640?wx_fmt=png

 现在,让我们测试一下,从下图中可以看到,当我们未获取 token 时,访问接口提示我们 401 Unauthorized,当我们模拟登录获取到 token 信息后,再次访问受保护的资源时,已经可以获取到响应的数据。之后,当我们刷新 token,此时再用原来的 token 信息访问时,已经无法访问,提示 403 Forbidden,同时,可以看到我们的 Redis 中已经存在了停用的 token 信息,此时,使用新的 token 信息又可以访问了。

  至此,整个的 Jwt 授权鉴权相关的代码就已经完成了,因为篇幅原因,完整的代码请到 Github 上进行查看(电梯直达)。PS:因为博客园允许上传的图片限制最大尺寸为 10M,所以这里上传的 gif 是压缩后的,见谅见谅,如果有需要查看清晰的图片,欢迎到我的个人博客上查看(电梯直达)。

、总结

   本章,主要是使用 Jwt 完成对于用户的授权与鉴权,实现了对于用户 token 令牌的创建、刷新、停用以及校验。在实际的开发中,采用成熟的轮子可能是更好的方案,如果你有针对 Jwt 进行用户授权、鉴权更好的解决方案的话,欢迎你在评论区留言指出。拖了很久,应该是年前的最后一篇了,提前祝大家新年快乐哈~~~

原文地址: https://www.cnblogs.com/danvic712/p/10331976.html

.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com
640?wx_fmt=jpeg


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/317435.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[2021-06-19] 提高组新手副本Ⅱ(联网,欧几里得,分解树,开关灯)

文章目录考试心路历程联网titlesolutioncode欧几里得titlesolutioncode分解树titlesolutioncode开关灯titlesolutioncode考试心路历程 佛了佛了&#xff0c;caocaocaocaocaocao 人直接炸嗨升天 并查集直接送走200200200分&#xff01;&#xff01;&#xff01;我屮艸芔茻 T1二…

在.NET Core中设计自己的服务宿主(Service Hosting)框架

很多时候我们都会有设计一个后台服务的需求&#xff0c;比如&#xff0c;传统的Windows Service&#xff0c;或者Linux下的守护进程。这类应用的一个共同特点就是后台运行&#xff0c;并且不占用控制台界面。通常情况下&#xff0c;后台服务在提供服务时&#xff0c;会通过日志…

CF1131 G. Most Dangerous Shark(DP+单调栈优化)

文章目录problemsolutioncodeproblem solution dpi:dp_i:dpi​: 前iii个多米诺骨牌全都倒下的最小花费 li,ril_i,r_ili​,ri​分别表示第iii个多米诺骨牌倒下时所能波及到的最左/右位置 往左倒&#xff0c;则[li,i)[l_i,i)[li​,i)内的牌都可以选择性地先推倒 dpimin⁡{dpjcos…

Cat Virus

Cat Virus 题意&#xff1a; 让你构造一颗树&#xff0c;要求如果一个点为黑&#xff0c;其子树全为黑&#xff0c;白点任意&#xff0c;现在让你构造一棵树&#xff0c;使其染色方案数为K&#xff0c;节点尽可能少 题解&#xff1a; 首先画出k<9的全部情况&#xff0c;并…

微软发布XAML Studio工具:快速构建UWP XAML原型

IT之家1月30日消息 微软车库的最新项目XAML Studio已经在Windows 10应用商店上架&#xff0c;将帮助开发人员快速构建UWP XAML原型&#xff0c;以后可以轻松地将其复制到Visual Studio中。它将允许开发人员实时预览他们的XAML代码&#xff0c;并与结果进行交互&#xff0c;就像…

.NET Core 3 Preview 2发布,C#8更强大的模式匹配

.NET Core 3 Preview 2 发布了&#xff0c;此版本主要带来了 C# 8 相关的新功能&#xff0c;C# 8 Preview 2 是 .NET Core 3 SDK 的一部分。C# 8 中使用模式进行更多操作&#xff0c;主要特性包括&#xff1a;using 声明改变需要缩进代码的方式&#xff0c;现在可以编写以下代码…

CF407 E. k-d-sequence(线段树+单调栈)

文章目录CF407 E. k-d-sequenceproblemsolutioncodeCF407 E. k-d-sequence problem solution special case&#xff0c;d0d0d0&#xff0c;相当于寻找最长的一段数字相同的区间 other case&#xff0c;如果要满足公差为ddd等差序列 区间内每个数在模ddd意义下同余每个数互不…

D. Binary Literature

D. Binary Literature 题意&#xff1a; 给三个长度为2 * n的01串&#xff0c;让你构造一个长度小于3 * n的字符串&#xff0c;使得这个串至少包含两个01串 题解&#xff1a; 很巧妙的构造题 三个指针分别指向三个串&#xff0c;因为是01串&#xff0c;所以一定存在两个字符…

安逸:鼠绘《诗与远方》

【作品名称】《诗与远方》【作者介绍】徐安&#xff08;笔名安逸&#xff0c;常州&#xff09;&#xff0c;PPT专家&#xff0c;鼠绘专家。平面设计专业&#xff0c;6年PPT设计经验&#xff1b;历届江苏省PPT制作大赛一等奖获得者&#xff0c;PA口袋动画重要合作人。PPT动画制作…

[HNOI2016]网络(树链剖分+线段树+大根堆)

[HNOI2016]网络 problem solution 另辟蹊径&#xff0c;不把交互请求赋在新增路径上&#xff0c;反而把交互请求赋在树上除去该请求路径覆盖点的其它点上 显然&#xff0c;路径问题树剖是非常可以的、 那么一个点上的信息就表示所有不经过该点的交互请求&#xff0c;用堆…

IdentityServer4实战 - JWT Token Issuer 详解

一.前言本文为系列补坑之作&#xff0c;拖了许久决定先把坑填完。下文演示所用代码采用的 IdentityServer4 版本为 2.3.0&#xff0c;由于时间推移可能以后的版本会有一些改动&#xff0c;请参考查看&#xff0c;文末附上Demo代码。本文所诉Token如无特殊说明皆为 JWT Token。众…

P3834 【模板】可持久化线段树 2(整体二分做法)

P3834 【模板】可持久化线段树 2&#xff08;主席树&#xff09; 我们详细讲讲这个整体二分如何求区间第k小 我们都知道二分可以求出区间里某个想要的值&#xff0c;如果有很多询问&#xff0c;我们对每个询问都进行二分&#xff0c;复杂度就是O(QNlog(1e9))铁超&#xff0c;那…

胡浩:人人能学的AI《从零开始机器学习》苏州.NET俱乐部课程分享

【课程名称】《从零开始机器学XI》【老师介绍】胡浩&#xff0c;微软最有价值专家&#xff08;MVP&#xff0c;十余届多方向&#xff09;&#xff0c;微软技术大会讲师。云、数据中心基础架构、全栈虚拟化、企业移动管理等领域的架构师及顾问。AI/ML等新技术的爱好者&#xff0…

IdentityServer4实战 - 与API单项目整合

一.前言我们在实际使用 IdentityServer4 的时候&#xff0c;可能会在使用 IdentityServer4 项目添加一些API&#xff0c;比如 找回密码、用户注册、修改用户资料等&#xff0c;这些API与IdentityServer4怎么共存在一个项目呢&#xff1f;二.整合1.首先在 Startup.cs 中添加 Ide…

[HDU 6157]The Karting(DP)

[HDU 6157]The Karting description solution 先用前缀和求出di:1→id_i:1\rightarrow idi​:1→i 的距离 前缀和满足&#xff1a;若在iii点进行方向改变&#xff0c;则iii产生的贡献是一定的&#xff0c;可以先累计贡献 也就是说真正的路径怎么走&#xff0c;我们是不关心…

.NET 开源简史

现在在微软开发开源软件是很一件正常的事情——但在 2007 年&#xff0c;当时我刚加入微软&#xff0c;那时候可不是这么一回事。微软花了好几年时间才找到正确的方向&#xff0c;让微软这艘大船顺着开源之风向前航行。现在回头远望过去那些曾经面临的挑战&#xff0c;我们一笑…

P2617 Dynamic Rankings(整体二分)

P2617 Dynamic Rankings 题意: 待修改的区间最值问题 题解&#xff1a; 整体二分天然带有修改性 整体二分做不带修改的区间最值—>看这里 现在待修改&#xff0c;我们可以将第l位修改为x&#xff0c;因为我们是用树状数组来维护的&#xff0c;所以把这个过程拆分成将第l个…

[LOJ 6042]「雅礼集训 2017 Day7」跳蚤王国的宰相(树的重心+贪心)

[LOJ 6042]「雅礼集训 2017 Day7」跳蚤王国的宰相 description solution 一个到所有节点距离和最小的节点 ⇔\Leftrightarrow⇔ 树的重心&#xff08;满足最重的儿子最轻&#xff0c;每个儿子siz≤n2\le\frac{n}{2}≤2n​&#xff09; 显然原树的重心答案为0 对于点iii&am…

由优劣语言之争引起的思考

由优劣语言之争引起的思考#欲使其灭亡&#xff0c;必使其疯狂昨天上午由阿里云中间件公众号和架构师小秘圈公众号发布的一篇文章《天天敲代码会使人变聪明么》在.net开发者中掀起了一阵巨浪&#xff0c;文章中提到的语言的先进与落后之争让基于.net开发者们义愤填膺&#xff0c…

[LOJ #521]「LibreOJ β Round #3」绯色 IOI(抵达)(结论)

#521. 「LibreOJ β Round #3」绯色 IOI&#xff08;抵达&#xff09; description solution 因为点的庇护所不能为自身&#xff0c;题目背景在树上&#xff0c;有结论一定是两个相邻点互为庇护所 所以树一定要能两两完美匹配才有解 判断完有解后就是构造解了&#xff0c;…