如何简单的在 ASP.NET Core 中集成 JWT 认证?

前情提要:ASP.NET Core 使用 JWT 搭建分布式无状态身份验证系统

文章超长预警(1万字以上),不想看全部实现过程的同学可以直接跳转到末尾查看成果或者一键安装相关的 nuget 包

自上一篇介绍如何在 ASP.NET Core 中集成 JWT 的博文发布接近一年后,我又想来分享一些使用 JWT 的经验了。过去的一年中,我每次遇到一些小的,垃圾的项目,就会按照去年的那片文章来进行配置,虽然代码不多,但是每次写这么一些模板代码,又感觉很枯燥、冗余,而且稍不注意就有可能配置的有问题,导致验证不成功。前几天,我继续写自己的垃圾毕设,写到集成 JWT 的时候,我终于忍受不了这种重复的配置工作了,于是便着手封装一个简单易用的 JWT 插件。

之前集成 JWT 的方法在 ConfigureServices 方法里面添加了太多细节上的东西,所以在新的实现里面,添加服务依赖的 API 一定要足够简单,其次,之前的实现里面,签发一个 Token 步骤太多且比较复杂,所以签发 Token 的步骤也要简化。最后,之前在 Cookie 中添加 JWT 支持也比较 hack,跟 ASP.NET Core 的集成也不是很好。带着这些痛点,我在网上经历了一番搜索,最终找到了这个仓库 ,本来都想直接用他的实现了,不过他的配置看起来还是有些麻烦的,所以没办法,只好自己手写一个了。

从设计配置 API 开始

其实不管是我之前写的实现还是 GitHub 上找到的那个仓库的实现,最让我不满意的地方就是配置,很多时候,我就只想快速地搭建一个项目,根本不想去研究“怎样配置”,所以我的第一步的目标就是设计一个简单的配置接口:

640?wx_fmt=png

EasyJwtOption 是用来进行描述 EasyJwt 配置的类型,它的每个属性都是我们可以进行配置的地方,同时为了避免把 ASP.NET Core 自带的对 JWT 跟 Cookie 的配置项目重写一遍,我就定义了 CookieOptions跟 JwtOptions 这两个属性,用来向微软的 AuthenticationBuilder 传递配置。

GenerateKey() GenerateCredentials() 这两个抽象方法则跟加密算法相关,在 JWT 中,我们可以使用两类算法进行加密:对称加密与非对称加密,在我之前写的文章中,我使用的是非对称加密的 RSA 算法,将原先的配置写成新的 EasyJwtOption 就是:

640?wx_fmt=png

由于 RSA 算法的私钥与密钥只能机器生成,所以我还是延续了以前的做法,把算法参数导出成 json 保存在本地,故 EasyRSAOptions 的构造函数接受一个存储位置作为必须参数。但是这种做法普适性不太好,更好的做法是把 RSA 私钥与公钥导出成标准格式的文本,这样其他的应用也可以导入,不过我比较懒,先这么凑活吧。

在 GitHub 找到的那个项目中,作者使用的是对称加密算法,把这个算法改成我的 EasyJwtOption 就是:

640?wx_fmt=png

在非对称加密算法中,我们需要提供一个密钥供加密、解密使用,所以 EasySymmetricOptions 的构造函数接受一个任意的字符串作为参数,又因为 SymmetricSecurityKey 对安全性的要求,密钥的长度太短会报出异常,用户的输入的密钥字符串进行了一些转换,来满足密钥长度条件。

方便的签发 Token

为了能够让网站的各个组件能够方便的随时签发 Token ,我设计了下面这个类,它的构造函数接受一个 EasyJwtOption 作为参数:

640?wx_fmt=png

只要我们在 Starpup.ConfigureServices 方法中把这个类添加进 IoC 容器,任何依赖 EasyJwt 的对象都可以非常简便的为用户生成 Token,调用方法大致如下:

640?wx_fmt=png

claims 是 Identity 中的概念,表示用户的信息,例如:用户名、邮箱。签发 token 需要指定用户名、用户相关的信息以及 token 过期时间。我们 EasyJwt 得到了签发 token 所需要的参数后会创建一个 ClaimsIdentity 对象,这同样也是 Identity 中的概念,用来表示用户的一些身份信息的集合,我们可以把一个 Identity 对象想象成一张通行证,上面记录着用户的身份信息。一个用户可以有多张通行证,这些通行证既可以由我们自己的应用生成,也可以由第三方授权的应用生成,不过具体的细节就涉及到了 Identity 的身份认证设计,在此就不拓展来讲了。

为应用添加 JWT 认证支持

上面说了这么多还只是停留在签发 Token 的阶段,进行身份认证从这里才真正开始。微软早就已经提供了一个添加 JWT 认证支持的拓展,不过那个还不算特别的简单易用,所以我就在微软的 API 之上设计了一个新的拓展方法来结合之前的 EasyJwt 配置 JWT 认证:

640?wx_fmt=png

这个拓展方法接受一个 EasyJwtOption 的子类实例作为参数,并通过这个参数初始化一个 EasyJwt 对象,并将其添加进 IoC 容器中。接着就是简单的调用微软的拓展方法,为应用程序添加 JwtBearer 认证。这里的 jwtParams 是由 EasyJwt 对象导出的,具体的导出代码实现可以在我的 GitHub 上看到,并不是很重要的代码,所以就不在这里贴出来了。

为 Cookie 添加 JWT 支持

为 Cookie 添加 Jwt 支持是最让人头疼的了,而且还要让我

们的 API 跟 ASP.NET Core 自己的机制能够较完美的结合起来,这里就需要比较多的代码了。

首先我们需要自定义一个 Cookie 中存储 Jwt Token 的格式,也就是下面这个 EasyJwtAuthTicketFormat

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

这个类我借鉴了前面提到的 Github 上面的那个项目的实现,并花了一些功夫对它做了一些改动。你可以看到,这个真的是非常大的一坨代码,不过我们还是先克服困难,从构造函数来阅读吧。

EasyJwtAuthTicketFormat 的构造函数接受三个参数,第一个我们已经见过了,是 EasyJwt 导出的 TokenValidationParameters,用来对 Jwt token 进行验证、解密。另外两个参数与 ASP.NET Core 的安全机制有关,IDataSerializer<AuthenticationTicket> ticketSerializer 用来将要存入 Cookie 中的数据序列化或者从 Cookie 中反序列化我们需要读出来的数据。IDataProtector dataProtector 则是用来对 Cookie 进行加密、解密的工具。

据 Github 上那个项目的作者说,他的代码是从微软的默认实现里面魔改出来的,所以我个人认为其中有些东西对于 Jwt 来说其实不是必须的,理由我会在下面详细解释。

首先一起来看看 Unprotect 方法,他的 protectedText 参数就是存储在 Cookie 中的字符串,首先我们需要对他用 Base64 进行解码,然后接着要用之前的 dataProtector 进行解密,最后再用 ticketSerializer 反序列化出 AuthenticationTicket 对象,这个 AuthenticationTicket 中存储的就是一些跟身份认证相关的数据,在我们这里,主要就是存储着 jwt Token。当我们把 token 中的用户数据解密并提取出来之后,再跟 Cookie 中可能含有的其他的身份信息合并起来(虽然可能并不会有什么其他信息。。。),最终就把结果返回出去。

Protect 方法就很简单了,基本上就是 Unprotect 开头一部分的逆序,先把 AuthenticationTicket序列化,然后使用 dataProtector 加密,最终 Base64 编码成字符串返回出去。

那么很有意思的事情就出现了,jwt 本身的设计就是可以直接在 HTTP 协议中直接传递的,一般来说,并不需要我们重新对其进行 Base64 编码,而且 JWT 本身的内容就是有加密校验的,也就是说信息可读但是不可被修改,那么使用 dataProtector 对其加密的过程也应该是可以省略的。不过由于我比较懒,而且对这里不太肯定,所以就没有移除这部分的代码。

向响应头 Cookie 中添加 JWT

你可能觉得这有啥意思,不就是直接 Cookies.Add() 就好了?然而这样做是没法让认证中间件正确的提取出 Token 的,我们需要用到 HttpContext.SignInAsync 这个方法。这个方法的一个重载是接受一个 ClaimsPrincipal 跟一个 AuthentifactionProperties 作为参数,而这两个东西就是上一节提到的 AuthenticationTicket 的重要组成。

所以,我们除了要让 EasyJwt 签发 token 之外,还要它能够生成 AuthTicket,方便我们跟自带的认证中间件结合使用,相关的实现代码如下:

640?wx_fmt=png

这个方法跟签发 Token 的方法长得一个样,接受一个 Claims 集合,然后用这些 claims 构建出一张通行证(ClaimsIdentity),然后把这个 identity 对象扔进一个 ClaimsPrincipal 里面。同时,我们还需要把 token 塞进一个 AuthentifactionProperties 对象里面。最后,把这两个创建出来的东西返回出去。

为了能够简化这部分的调用,我又写了一个拓展方法把 SignInAsync 重新包装了一下:

640?wx_fmt=png

这样,在用户登录的时候就可以非常的简单的同时把 token 显式的返回并设置在 Cookie 中了:

640?wx_fmt=png

看起来我们终于能够正确的签发 token 了,然而事情并没有结束,我们还没有把 Cookie 认证及其相关依赖添加到 IoC 容器中,让我们直接修改一下前面的操作注册服务的拓展方法好了:

640?wx_fmt=png

至此,我们终于能够完整的让 Jwt 的功能运行起来了。

成果展示

那么如何在一个空白的项目中使用 EasyJwt 认证呢?

1. 注册服务

640?wx_fmt=png

2. 添加认证中间件

// Startup.Configureapp.UseAuthentication();

3. 编写自己的登录注册控制器

640?wx_fmt=png

4. 使用 EasyJwtAuthorize 认证过滤器保护你的 API 或者 MVC 控制器

640?wx_fmt=png

终于,经过我们一系列的魔改,我们可以非常快速的来构建一个使用 Jwt 来进行身份认证的网站了。

本文的全部代码您都可以在我的这个项目中找到,或者,如果您想在您的项目中试试我写的这个小拓展,可以直接使用 dotnet cli 来安装:

dotnet add package ZeekoUtilsPack.AspNetCore --source https://www.myget.org/F/zeekoget/api/v3/index.json 

可以改进的地方

  1. 加入吊销 token 的功能

  2. 移除 EasyJwtAuthTicketFormat 中冗余的代码

原文地址:https://www.cnblogs.com/JacZhu/p/9388964.html


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

640?wx_fmt=jpeg

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

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

相关文章

【二分】【暴力】蛋糕(gmoj 3918)

蛋糕 gmoj 3918 题目大意&#xff1a; 有一个蛋糕&#xff0c;分成n∗mn*mn∗m个单位&#xff0c;现在横竖各切三刀&#xff0c;使其分成16个矩阵&#xff0c;使价值最小的矩阵价值最大 输出样例 5 5 95998 21945 23451 99798 74083输入样例 3数据范围 40%的数据&#x…

Music Problem

文章目录题目描述题意&#xff1a;题解&#xff1a;传送时间限制&#xff1a;C/C 2秒&#xff0c;其他语言4秒 空间限制&#xff1a;C/C 131072K&#xff0c;其他语言262144K 64bit IO Format: %lld 题目描述 Listening to the music is relax, but for obsessive(强迫症), it …

可扩展架构设计的三个维度

业界对于可扩展的系统架构设计有一个朴素的理念,就是&#xff1a;通过加机器就可以解决容量和可用性问题这一理念在“云计算”概念疯狂流行的今天&#xff0c;得到了广泛的认可&#xff01;对于一个规模迅速增长的系统而言&#xff0c;容量和性能问题当然是首当其冲的。但是随着…

.NET Core开发日志——简述路由

有过ASP.NET或其它现代Web框架开发经历的开发者对路由这一名字应该不陌生。如果要用一句话解释什么是路由&#xff0c;可以这样形容&#xff1a;通过对URL的解析&#xff0c;指定相应的处理程序。回忆下在Web Forms应用程序中使用路由的方式&#xff1a;然后是MVC应用程序&…

博客开通

开通博客第一天&#xff0c;纪念一下——

.NetCore Cap 结合 RabbitMQ 实现消息订阅

开源分布式消息框架 Cap 可以在GitHub上拉也可以通过nuget添加上一篇博文写了 Windows RabbitMQ的安装使用 Cap支持事务&#xff0c;通过捕获数据库上下文连接对象实现 消息事务&#xff0c;消息持久化怎么来实现消息订阅 消费&#xff1f;使用起来非常简单&#xff0c;主要通过…

小H和游戏

文章目录题目描述题解&#xff1a;传送时间限制&#xff1a;C/C 2秒&#xff0c;其他语言4秒 空间限制&#xff1a;C/C 262144K&#xff0c;其他语言524288K 64bit IO Format:%lld 题目描述 小H正在玩一个战略类游戏&#xff0c;她可以操纵己方的飞机对敌国的N座城市(编号为1~N…

asp.net core 发布到 docker 容器时文件体积过大及服务端口的配置疑问

在 asp.net core 发布时&#xff0c;本人先后产生了3个疑问。1、发布的程序为什么不能在docker容器中运行当时在window开发环境中发布后&#xff0c;dotnet xxx.dll可以正常运行&#xff1b;但放入docker容器后就报 *.*.deps.json not found 的错误。后根据下面的文章解决了问题…

水题(water)(非详细解答)

传送 时间限制&#xff1a;C/C 1秒&#xff0c;其他语言2秒 空间限制&#xff1a;C/C 32768K&#xff0c;其他语言65536K 64bit IO Format: %lld 题目描述 其中&#xff0c;f(1)1;f(2)1;Z皇后的方案数&#xff1a;即在ZZ的棋盘上放置Z个皇后&#xff0c;使其互不攻击的方案数。…

网络流小结

最大流&#xff1a; EK算法&#xff1a; #include<iostream> #include<cstring> #include<cstdio> #include<queue> using namespace std; const int inf0x7fffffff; const int maxn10010; struct node{int u,v,f,next; }edge[300050]; int s,t,cnt,…

.NET MVC CSRF/XSRF 漏洞

最近我跟一个漏洞还有一群阿三干起来了……背景&#xff1a;我的客户是一个世界知名的药企&#xff0c;最近这个客户上台了一位阿三管理者&#xff0c;这个货上线第一个事儿就是要把现有的软件供应商重新洗牌一遍。由于我们的客户关系维护的非常好&#xff0c;直接对口人提前透…

jzoj5057-[GDSOI2017模拟4.13]炮塔【网络流,最大权闭合图】

正题 题面链接:https://gmoj.net/senior/#main/show/5057 题目大意 n∗mn*mn∗m的网格上有一些炮和敌军&#xff0c;每个炮可以攻击在它方向上一个敌军&#xff0c;但是要求炮弹的轨迹不能交叉。求最多打死多少敌军。 解题思路 我们先把炮分成两类&#xff0c;一类是横着打&a…

讲一下Asp.net core MVC2.1 里面的 ApiControllerAttribute

正文ASP.NET Core MVC 2.1 特意为构建 HTTP API 提供了一些小特性&#xff0c;今天主角就是 ApiControllerAttribute。0. ApiControllerAttribute 继承自 ControllerAttributeASP.NET Core MVC 已经有了ControllerAttribute&#xff0c;这个用来标注一个类型是否是Controller。…

新的UWP和Win32应用程序分发模型

自2005年引入ClickOnce技术以来&#xff0c;.NET就支持应用程序自动升级。在ClickOnce模型中&#xff0c;WinForms和WPF应用程序在启动时会从预先配置好的位置查找新版本。但是&#xff0c;由于微软试图模仿iOS应用商店模型&#xff0c;所以&#xff0c;该模型未能延续到UWP。微…

.net core grpc 实现通信(一)

现在系统都服务化&#xff0c;.net core 实现服务化的方式有很多&#xff0c;我们通过grpc实现客户端、服务端通信。grpc(https://grpc.io/)是google发布的一个开源、高性能、通用RPC&#xff08;Remote Procedure Call&#xff09;框架&#xff0c;使用HTTP/2协议&#xff0c;…

子序列

牛客网题目 题目描述 给出一个长度为n的序列&#xff0c;你需要计算出所有长度为k的子序列中&#xff0c;除最大最小数之外所有数的乘积相乘的结果 输入描述: 第一行一个整数T&#xff0c;表示数据组数。 对于每组数据&#xff0c;第一行两个整数N&#xff0c;k&#xff0c;含义…

横向扩展你的ASP.NET Core SignalR 应用

前言最近项目要用signalr来做实时通信&#xff0c;在研究asp.netcore signalr 应用横向扩展时候发现了这篇国外的博客&#xff0c;和大家分享一下原文连接地址负载均衡当你把你的应用部署到生产环境时&#xff0c;你将会想横向扩展你的应用。横向扩展意味着要你的应用要在多台服…

池化层(pooling)

目录 一、池化层 1、最大池化层 2、平均池化层 3、总结 二、代码实现 1、最大池化与平均池化 2、填充和步幅(padding和strides) 3、多个通道 4、总结 一、池化层 1、最大池化层 2、平均池化层 3、总结 池化层返回窗口中最大或平均值环节卷积层对位置的敏感性同样有窗口…

牛客网【每日一题】4月16日题目精讲 逆序对

文章目录题目描述题解&#xff1a;代码传送时间限制&#xff1a;C/C 1秒&#xff0c;其他语言2秒 空间限制&#xff1a;C/C 131072K&#xff0c;其他语言262144K 64bit IO Format:%lld 题目描述 求所有长度为n的01串中满足如下条件的二元组个数&#xff1a; 设第i位和第j位分别…

[BZOJ1095][ZJOI2007]捉迷藏 Query on a tree IV(树链剖分)

首先&#xff0c;我们求出树的重链&#xff0c;然后对于每一条链&#xff0c;建一颗线段树 树大概长这样&#xff1a; &#xff08;其中用红边连起来的是一条条重链&#xff09; 在线段树上&#xff0c;我们维护&#xff1a; Opt(u)&#xff1a;经过 u节点代表的链的其中一段 …