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,一经查实,立即删除!

相关文章

AT2293-[AGC009D] Uninity【贪心,状压】

正题 题目链接:https://www.luogu.com.cn/problem/AT2293 题目大意 给出一棵树&#xff0c;求它一棵点分树的最小深度。 1≤n≤1051\leq n\leq 10^51≤n≤105 解题思路 点分树的做法是直接找重心&#xff0c;但是两个重心我们很难确定找哪个&#xff0c;所以这个方法行不通。…

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

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

模板:扫描线

那看似平凡的面积&#xff0c;是多少条线的织物啊 前言 突然发现自己之前没发过扫描线的模板 可能是因为之前的实现太差了 这次感觉实现的还是很不错的&#xff0c; 虽然常数比较大&#xff0c;但是好写啊&#xff01; 原来扫描线也是可以1A的 线段树上利用zld讲解的维护最小…

P4887-[模板]莫队二次离线(第十四分块(前体))

正题 题目链接:https://www.luogu.com.cn/problem/P4887 题目大意 给出一个长度为nnn的序列aaa。mmm次询问[l,r][l,r][l,r]求有多少个l≤i<j≤rl\leq i< j\leq rl≤i<j≤r满足aixoraja_i\ xor\ a_jai​ xor aj​二进制下恰好有kkk个111。 1≤n,q≤105,0≤ai,k<21…

在.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;并…

NOIP2021:游记

前言 似乎前一秒还在为接下来的考试紧张&#xff0c;下一秒就已经走出了考场 恍惚之间突然意识到&#xff0c;有些日子&#xff0c;可能真的变成了过往 停止emo 感谢FFC&#xff0c;感谢大连&#xff0c;使这次考试顺利进行 主要的问题是T4的暴力 写的时候只有不到一个点&…

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

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

P5113-Sabbat of the witch【分块,基数排序】

正题 题目链接:https://www.luogu.com.cn/problem/P5113 题目大意 一个长度为nnn的序列aaa&#xff0c;mmm次要求支持以下操作 将区间[l,r][l,r][l,r]都变为xxx。询问区间[l,r][l,r][l,r]的和。将第xxx次操作111撤销。 强制在线 1≤n,m≤105,1≤ai,x≤1091\leq n,m\leq 10^…

C. The Sports Festival

C. The Sports Festival 题意&#xff1a; n个数&#xff0c;依次将所有数加入到区间内&#xff0c;每次得到一个k&#xff0c;k等于当前区间的最大值减最小值&#xff0c; 求所有k的和的最小值 题解&#xff1a; 一开始就没往dp那方面想&#xff0c;自己在dp这方面的理解还…

模板:点分治点分树

文章目录前言点分治背景解析代码点分树情境代码thanks for reading!所谓点分治&#xff0c;就是把所有的点分开来治 &#xff08;逃&#xff09; &#xff08;应广大观众要求&#xff0c;开篇废话改回原风格qwq&#xff09; 前言 很神奇的算法。 没有引入任何新的知识&#x…

.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;现在可以编写以下代码…

Loj#2474-「2018 集训队互测 Day 3」北校门外的未来【LCT】

正题 题目链接:https://loj.ac/p/2474 题目大意 开始有一个只有点111的图&#xff0c;一个点xxx能走到点yyy当且仅当路径(x,y)(x,y)(x,y)之间&#xff08;不包括x,yx,yx,y&#xff09;不存在编号比xxx或yyy要大的节点。有mmm次操作&#xff1a; 新建一个编号为yyy的节点和xx…

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动画制作…

洛谷P4292:重建计划(点分治、单调队列)

解析 第一眼&#xff1a;Wow这么水的黑&#xff1f;&#xff1f; 然后写了一发二分套线段树的3log代码上去 T到飞起&#xff0c;只有40… 无奈瞅了一眼标签&#xff1a;单调队列 对啊 于是又写了一个上去 20 … 好啊 然后就摆烂了 qwq 果然黑题没有一个好东西 一个关键的思…

E. Colorings and Dominoes(未解决)

E. Colorings and Dominoes 题意&#xff1a; n * m的格子&#xff0c;分为黑白格子&#xff0c;白格子可以染成蓝色或者红色&#xff0c;一个1 * 2的多米诺骨牌&#xff0c;可以覆盖两个连续的水平的红色格子或者两个连续的竖着的蓝色格子&#xff0c;对于一个染色方案&…

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

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