ASP.NET Core 基于角色的 JWT 令牌

原文:https://bit.ly/3vYljq3
作者:Rick Strahl
翻译:精致码农-王亮
声明:我翻译技术文章不是逐句翻译的,而是根据我自己的理解来表述的。其中可能会去除一些本人实在不知道如何组织但又不影响理解的句子。

ASP.NET Core 中的认证和授权仍然是配置中最麻烦的组件。似乎几乎在每一个应用程序上,我都会遇到一些与 Auth 有关的问题。四个版本带来了三种不同的身份验证实现,功能的更新也留下了一大波过时的信息。今天,我看着 Web API 基于角色 JWT 授权认证的过时信息,陷入了一个土拨鼠日(译注:形容不断重复的日子)的循环中。

目前在 ASP.NET Core 中的 JWT 令牌(Token)配置实际上非常好用,只要你把正确的配置咒语串起来。Auth 配置的部分问题是,大多数配置只需按固定的“仪式”进行操作。例如,设置IssuerAudience我们似乎完全不需要关心它们是什么,但它们是 JWT 令牌要求的一部分,确实需要配置。幸运的是,这些设置中只有少数几个是真正需要的,大部分都是模板。

在这篇文章中,我具体讲一下:

  • ASP.NET Core Web API 的认证

  • JWT 令牌的使用

  • 基于角色授权

  • 只使用底层功能--不使用 ASP.NET Core Identity

配置

认证(Authentication)和授权(Authorization)在 ASP.NET Core 中作为中间件提供,你必须在ConfigureServices()中配置它们,并在Configure()中连接中间件。

配置 JWT 认证和授权

第一步是在Startup文件中的ConfigureServices()中配置认证(Authentication)。在这里添加 JWT 令牌配置,并将所需组件添加到 ASP.NET Core 的处理管道中:

// in ConfigureServices()// config shown  for reference values
config.JwtToken.Issuer = "https://mysite.com";
config.JwtToken.Audience = "https://mysite.com";
config.JwtToken.SigningKey = "12345@4321";  //  some long id// Configure Authentication
services.AddAuthentication( auth=>
{auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{options.SaveToken = true;options.TokenValidationParameters = new TokenValidationParameters{ValidateIssuer = true,ValidIssuer = config.JwtToken.Issuer,ValidateAudience = true,ValidAudience = config.JwtToken.Audience,ValidateIssuerSigningKey = true,IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.JwtToken.SigningKey))};
}

JWT 认证有一堆的设置,其中大部分是足够神秘的,所以我几乎只是将它们复制和粘贴。我只想说,这些设置大多是关于设置协议和令牌包装器(Wrapper)的。通常情况下,我将这些值存储在我的应用程序的配置中,这样它就会通过 .NET 配置 Provider 提取进来,而上面的config就是那个特定的配置实例。

在这个全局配置中没有什么是针对角色的。所有基于角色的相关配置都发生在后面的认证(Authenticate)端点中创建令牌的时候。

令牌和哈希如何工作

在进入这里之前,我们先来回顾一下基于令牌的身份验证是如何工作的,以及这些设置值是如何融入这个方案的。

上面的设置值配置了令牌的常用值和用于签署令牌的密钥。它们提供身份识别标记,以确保生成的令牌是唯一的。我认为这些值是一个基本的令牌包装,通常在你验证用户后,当你创建令牌并将令牌作为 Web 请求的一部分提供给用户之时,你将向令牌添加你的自定义、应用特定的 Claim,。

IssuerSigningKey是这个配置中最重要的部分,它用于将最终的令牌与包装器以及任何添加的声明进行哈希(Hash)。该哈希值用于验证令牌的真实性。请注意,虽然生成的令牌被编码为 Base64,但它本身并不安全,即使在客户端,内容也可以被解码。也就是说,你可以将任何 JWT 令牌粘贴到 JWT.io 这个网站中,对令牌的内容进行解码。

哈希确保了令牌不能被改变。当令牌与请求一起发送时,它将由 ASP.NET Core 的 JWToken 中间件进行验证,它首先根据令牌数据验证哈希值,然后根据包含的授权信息进行认证/授权。如果客户端或其他实体以任何方式更改了令牌,则哈希值将无法验证通过,会被直接拒绝。之后在中间件管道的授权部分进行用户名和角色等的匹配。

添加 Auth 中间件

接下来我们需要在Startup文件的Configure中使用app.UseAuthentication()app.UseAuthorization()添加实际的中间件:

// in Startup.Configure()
app.UseHttpsRedirection();
app.UseRouting();// *** These are the important ones - note order matters ***
app.UseAuthentication();
app.UseAuthorization();app.UseStatusCodePages();
//app.UseDefaultFiles(); // so index.html is not required
//app.UseStaticFiles();app.UseEndpoints(endpoints =>
{endpoints.MapControllers();
});

请注意,顺序对于认证(Authentication)和授权(Authorization)很重要。这两个需要在 Routing 之后但在任何 HTTP 输出中间件之前添加,最重要的是在app.UseEndpoints()之前。

使用 Web API 端点认证用户

接下来,我们需要在应用程序中通过询问凭证来验证用户,然后生成一个令牌并将其返回给 API 客户端。

这很可能发生在 Controller 的 Action 方法或中间件端点处理程序中。下面是使用 Controller 的 Action 方法示例:

[AllowAnonymous]
[HttpPost]
[Route("authenticate")]
public object Authenticate(AuthenticateRequestModel loginUser)
{// My application logic to validate the user// returns a user entity with Roles collectionvar bus = new AccountBusiness();var user = bus.AuthenticateUser(loginUser.Username, loginUser.Password);if (user == null)throw new ApiException("Invalid Login Credentials: " + bus.ErrorMessage, 401);var claims = new List<Claim>();claims.Add(new Claim("Username",loginUser.Username));claims.Add(new Claim("DisplayName",loginUser.Name));// Add roles as multiple claimsforeach(var role in user.Roles){claims.Add(new Claim(ClaimTypes.Role, role.Name));}// Optionally add other app specific claims as neededclaims.Add(new Claim("UserState", UserState.ToString()));// create a new token with token helper and add our claim// from `Westwind.AspNetCore`  NuGet Packagevar token = JwtHelper.GetJwtToken(loginUser.Username,Configuration.JwtToken.SigningKey,Configuration.JwtToken.Issuer,Configuration.JwtToken.Audience,TimeSpan.FromMinutes(Configuration.JwtToken.TokenTimeoutMinutes),claims.ToArray());return new{token = JwtHelper.GetJwtTokenString(token),expires = token.ValidTo};
}

我正在使用一个JwtHelper类来实际生成一个令牌,这样我就不必在每个应用中记住JwtHelper类实现的这个重复的“仪式”。这段代码创建了令牌,并从中提取了一个字符串,准备作为承载令牌值返回。下面是这个类的完整代码:

public class JwtHelper
{/// <summary>/// Returns a Jwt Token from basic input parameters/// </summary>public static JwtSecurityToken GetJwtToken(string username,string uniqueKey,string issuer,string audience,TimeSpan expiration,Claim[] additionalClaims = null){var claims = new[]{new Claim(JwtRegisteredClaimNames.Sub,username),new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())};if (additionalClaims is object){var claimList = new List<Claim>(claims);claimList.AddRange(additionalClaims);claims = claimList.ToArray();}var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(uniqueKey));var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);return new JwtSecurityToken(issuer: issuer,audience: audience,expires: DateTime.UtcNow.Add(expiration),claims: claims,signingCredentials: creds);}/// <summary>/// Returns a token string from base claims/// </summary>public static string GetJwtTokenString(string username,string uniqueKey,string issuer,string audience,TimeSpan expiration,Claim[] additionalClaims = null){var token = GetJwtToken(username, uniqueKey, issuer, audience, expiration, additionalClaims);return new JwtSecurityTokenHandler().WriteToken(token);}/// <summary>/// Converts an existing Jwt Token to a string/// </summary>public static string GetJwtTokenString(JwtSecurityToken token){return new JwtSecurityTokenHandler().WriteToken(token);}/// <summary>/// Returns an issuer key/// </summary>public static SymmetricSecurityKey GetSymetricSecurityKey(string issuerKey){return new SymmetricSecurityKey(Encoding.UTF8.GetBytes(issuerKey));}
}

Controller 的Authenticate()代码首先使用一个应用程序特定的业务对象来验证用户,用户登录信息作为 API 调用的一部分传入该方法(比如 HTML 网页的登录表单)。如果用户是有效的,我就创建新的 Claim,这些 Claim 被打包到令牌中。

令牌包括用户名和角色,这是 ASP.NET Core 授权工作所需的内容。然后,如果有必要,我可以添加一些额外的应用程序特定的 Claim,比如上面例子中的DisplayName和自定义UserState对象。这些声明会随令牌一起,以便在后续请求提取,而不必再访问后端数据库检索它们。

最后,使用JwtHelperGetJwtToken()生成令牌,并使用GetJwtTokenString()将令牌转换为字符串,这个字符串将被客户端放在请求头中携带到后台服务端。

请注意,要确保可以匿名访问 Authentication 方法。如果 Controller 标注了 [Authorize] 特性,则需要在Authenticate()方法上标注[AllowAnonymous]特性。

Claim 和角色

ASP.NET Core 使用 Claim 进行认证。Claim 是你可以存储在令牌中的数据片段,这些数据与令牌一起携带,并可以从令牌中读取。对于授权来说,角色可以作为 Claim。

在 .NET Core 3.1 和 5.x 中,为授权添加 ASP.NET Core 角色识别的正确语法是,为每个角色添加多个 Claim:

// Add roles as multiple claims
foreach(var role in user.Roles)
{claims.Add(new Claim(ClaimTypes.Role, role.Name));// these also work - and reduce token size// claims.Add(new Claim("roles", role.Name));// claims.Add(new Claim("role", role.Name));
}

访问生成 JWT 令牌的 API

到这,我已经有了一个用于认证的 API 端点,我可以从这个端点上获取一个令牌。下面是这个请求的样子:

传入用户名和密码,则会返回令牌和到期时间。你可以在 jwt.io 查看这个令牌和它生成的内容:

请注意,该令牌很容易被外部工具解码,与我的应用程序完全无关。这意味着所包含的令牌数据是不安全的。然而,除非数据由原始的签名密钥签名,否则无法更改该令牌中的值并提供给服务器应用程序。这可以防止令牌被篡改。

一旦生成了令牌并发送给客户端,客户端就可以在后续的请求中使用它来添加相应的授权请求头:

Authorization: Bearer 123456******

确保 API 的安全

现在剩下的就是通过在 Controller 或端点方法上添加[Authorize]特性来选择性或限制对 API 的访问。

我可以使用以下特性之一,或者完全不使用特性(对于开放访问):

  • 普通的[Authorize]让任何经过认证的用户进入

  • 基于角色的[Authorize(Roles = "Administrator,ReportUser")]访问

  • 允许匿名[AllowAnonymous]访问

请注意,这些特性可以在 Controller 类或 Action 方法上标注,而且它们是自上而下分层工作的,所以一个类属性适用于所有的 Action 方法。这就是 [AllowAnonymous] 的用武之地,它可以覆盖一两个可能需要开放访问的请求(如Authenticate()Logout())。

要为任何登录用户设置授权,只需使用[Authorize]即可:

[Authorize]   // just require ANY authentication
[Route("/api/v1/lookups")]
public class IdLookupController : BaseApiController

在这种情况下,你可能需要对用户进行一些额外的验证,以确保你有正确的用户进行特定的操作。

要设置特定角色的限制,你可以使用Roles参数:

[Authorize(Roles = "Administrator")]
[HttpPost]
[Route("customers")]
public async Task<SaveResponseModel> SaveCustomer(IdvCustomer model)

现在只有那些属于 Administrator 组的人有访问权。角色可以是使用逗号分隔的列表,如使用“Administrator, ReportUser”来允许多个角色访问。

使用令牌访问安全端点

现在 API 已经安全了,我们必须在每个请求中传递 Bearer 令牌来进行验证。它看起来像这样:

瞧,我现在可以访问管理员组保护的 POST 操作了。

这就完成了一个闭环...

总结

在最近的版本中,ASP.NET Core 中的身份验证和授权已经变得简单了很多,但是要找到正确的文档来设置 JWT 令牌身份验证的所有相关信息仍然不易。关于身份验证的信息很多,很容易在文档中迷失方向,并最终可能选择过时的信息,因为在整个 ASP.NET Core 版本中,身份验证的行为已经发生了重大变化。(基于本文)如果你要查找额外的信息,请确保它是 3.1 及以后的版本。

在这篇文章中,我已经解决了 3.1 和 5.0 版本的问题。值得庆幸的是,5.0 没有看到对认证/授权 API 的进一步破坏性改变。

通常情况下,我写下这篇文章是为了让我自己安心,这样我就能在一个地方得到所有的信息。希望你们中的一些人也会觉得这很有用。

-

想第一时间看到更新推送,请帮忙点点『赞』和『在看』

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

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

相关文章

圆形比例分布图怎么做_解读宝山区2035总体规划:建设用地的比例在上海非中心城区中最高...

《上海市宝山区总体规划暨土地利用总体规划(2017-2035)》已经发布&#xff0c;在宝山区2035总体规划中&#xff0c;一个重要的核心指标成为了亮点&#xff0c;这就是宝山区的建设用地面积为244.3平方公里。宝山区的陆地总面积是301.6平方公里&#xff0c;由此宝山区建设用地面积…

Lanchester战争模型:用可分离变量的微分方程占卜战事

看过国产的战争题材电视剧《亮剑》的各位老铁一定熟悉李云龙集结重兵攻打县城的故事。在故事中&#xff0c;李云龙利用人数上的优势对平安县城进行了围点打援&#xff0c;最后用二营长的意大利炮消灭了城楼上的鬼子官。但是众所周知&#xff0c;抗日时期中国军队的单兵作战能力…

微软软件安装平台

http://www.microsoft.com/web/downloads/platform.aspx 转载于:https://www.cnblogs.com/kkun/archive/2011/03/09/1978039.html

如何在 C# 中使用 yield

yield关键词是在 C# 2.0 中被引入的&#xff0c;我们都知道实现了 IEnumerable 接口的类都可以用于被 foreach 迭代&#xff0c;这是因为 IEnumerable 接口中提供了一个可迭代的 GetEnumerator() 方法&#xff0c;代码定义如下&#xff1a;public interface IEnumerable{IEnume…

通过听力写代码?盲人程序员就是这样做的

一Michael Forzano&#xff0c;Amazon2018 年 3 月&#xff0c;Amazon 官网「工作在 Amazon」栏目有一篇文章&#xff0c;介绍了他们的一位盲人程序员 Michael Forzano。&#xff08;视频来自&#xff1a;阑夕&#xff09;Amazon 软件工程师 Michael Forzano 出生就因先天疾病而…

android修改电量颜色,android状态栏电池颜色?

鸿蒙传说/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java在Intent.ACTION_BATTERY_CHANGED broadcast处理中调用fireBatteryLevelChanged()方法fireBatteryLevelChanged中会回掉BatteryStateChangeCallbackframewor…

access 战地1不加入ea_炒牛肉时,想要牛肉嫩滑又不老,只需加入1样东西,很多人都不懂...

炒牛肉时&#xff0c;想要牛肉嫩滑又不老&#xff0c;只需加入1样东西&#xff0c;很多人都不懂冬季滋补怎么能少得了牛肉&#xff0c;牛肉中含有非常丰富的营养成分&#xff0c;其中蛋白质的含量尤为丰富还有大量的氨基酸&#xff0c;能够提高身体的抗病能力&#xff0c;特别适…

张亚勤:PC之外的争夺战

文 / 陈振烨 尽管经常有公司抢走微软的风头&#xff0c;但当今世界&#xff0c;微软仍然是当之无愧的IT霸主。过去几个季度&#xff0c;微软各项核心数据非常给力&#xff0c;2011财年第一季度净利润54亿美元&#xff0c;营收162亿美元&#xff1b;而2010财年187.6亿美元的净利…

优化 ASP.NET Core Docker 镜像的大小

在这容器化的世界里&#xff0c;我们已经很少直接通过文件发布来运行asp.net core程序了。现在大多数情况下&#xff0c;我们都会使用docker来运行程序。在使用docker之前&#xff0c;我们往往需要打包我们的应用程序。asp.net core程序的镜像打包&#xff0c;网上有很多教程&a…

监督学习:KNN(K-近邻)算法实现手写数字识别的三种方法

没人会看的开场白&#xff1a;本来觉得自己从数据建模转人工智能方向应该问题不大&#xff08;自我感觉自己算法学的不错&#xff09;。结果一个K-邻近实现手写数字识别的代码就让我改了三四天。虽然网上这方面的代码是很多&#xff0c;但是我运行了好几个&#xff0c;结果都不…

xp怎样安装android-studio,Xposed 框架的安装

借鉴&#xff1a;安装流程&#xff1a;安装 雷电模拟器3.93下载安装xposed框架。xposed 框架在Dalvik 虚拟机和ART虚拟机上的安装不一样(至于ART虚拟机和Dalvik虚拟机的区别&#xff0c;可以参阅 文章)如果Android 版本不小于 5.0&#xff0c;就是ART版本的虚拟机&#xff0c;需…

在 .NET Core 5 中集成 Create React app

翻译自 Camilo Reyes 2021年2月22日的文章 《Integrate Create React app with .NET Core 5》 [1]本文演示了如何将 Create React app 与 .NET Core 集成&#xff0c;以生成一个移除了几个依赖项的脚手架。Create React app 是社区中创建一个全新 React 项目的首选方式。该工具…

程序员找不到对象是因为还没遇到一个有远见的丈母娘

当别人在放肆秀恩爱的时候&#xff0c;程序员单身狗们在角落里瑟瑟发抖。别人去网站相亲找到对象&#xff0c;程序员去相亲找到BUG。其实&#xff0c;你找不到对象是因为你还没遇到一个有远见的丈母娘。都说程序员很难找到对象&#xff0c;就知道整天对着键盘一直敲敲敲&#x…

WPF 如何将IconFont图标转成Geometry

之前每次使用IconFont图标&#xff0c;都要去下载一个png图片&#xff0c;每次颜色什么的改了&#xff0c;都要重新下载&#xff0c;太苦逼了。现在好了&#xff0c;终于找到如何方便快速地使用IconFont图标了。是应该的演示如何从IconFont网站上找到Geometry先看看效果吧&…

给所有想从事软件研发的年轻工程师的忠告与建议

图片来源&#xff1a;Fargo Season 3一、我为什么写这篇文章&#xff1a;这几天&#xff0c;在某个IT论坛的软件培训与认证栏目中&#xff0c;看到了很多处于迷惑之中的人们&#xff0c;也看到了许多大家普遍感到困惑的问题&#xff0c;写此文章的目的&#xff0c;是想将我这些…

谁今天收到鸿蒙系统推送,鸿蒙系统正式推送,只有部分高端机才能收到

原标题&#xff1a;鸿蒙系统正式推送&#xff0c;只有部分高端机才能收到华为已经对鸿蒙2.0系统开始进行推送更新&#xff0c;从华为推出鸿蒙系统概念已经时隔几年了&#xff0c;如今华为把ppt系统映射进现实是真正为自己正名了&#xff0c;而且据华为陈述鸿蒙2.0已经能达到安卓…

2020邮箱账号密码大全_通知 | 复旦大学2020年春季学期研究生选课FAQ

1选课须知1. 研究生选课系统什么时间开放&#xff1f; 答&#xff1a;2020年春季学期研究生选课系统开放时间为&#xff1a;2020年2月19日(周三)10:00至2020年3月9日(周一)10:00。2020年3月9日(周一)10:00后&#xff0c;研究生可以在选课系统中查询课表、已选课程、学分获得情况…

如何从零开始构建深度学习项目?这里有一份详细的教程

导读&#xff1a;在学习了有关深度学习的理论之后&#xff0c;很多人都会有兴趣尝试构建一个属于自己的项目。本文将会从第一步开始&#xff0c;告诉你如何解决项目开发中会遇到的各类问题。本文由六大部分组成&#xff0c;涵盖深度学习 ( DL ) 项目的整个过程。我们将使用一个…

性能分布式NewLife.XCode对无限数据的支持

上周发布了《改进版CodeTimer及XCode性能测试》&#xff0c;展示了NewLife.XCode在性能上的表现。实际上NewLife.XCode是一个很平凡的ORM&#xff0c;只是在分页和缓存方面多下点功夫&#xff0c;注意每一个细节&#xff0c;才能保证在数据量大、业务繁忙的环境中得以保持良好的…

C#实用小知识:string和判断null

stringstring是常用的类型&#xff0c;它具有不可变性&#xff1a;就是一旦赋值&#xff0c;就不可变&#xff0c;如果再赋值 &#xff0c;就重新开辟内存空间&#xff1b;保留性&#xff1a;如果一个字符串存在&#xff0c;另一个与其相同&#xff0c;他们会指向同一个地址&am…