.NET 6.0中使用Identity框架实现JWT身份认证与授权

原文作者:Sarathlal Saseendran

原文链接:https://www.c-sharpcorner.com/article/jwt-authentication-and-authorization-in-net-6-0-with-identity-framework/

翻译:沙漠尽头的狼(谷歌翻译加持)

介绍

微软于 2021 年 11 月发布了 .NET 6.0。我已经在 C# Corner[1] 上写了几篇关于 JWT 身份认证的文章。由于 .NET 6.0 进行了一些重大更改,因此我决定写一篇关于使用 .NET 6.0 版本进行 JWT 身份认证的文章。我们将使用微软 Identity 框架来存储用户和角色信息。

Authentication(身份认证)是验证用户凭据的过程,而Authorization(授权)是检查用户访问应用程序中特定模块的权限的过程。在本文中,我们将了解如何通过实现 JWT 身份认证来保护 ASP.NET Core Web API 应用程序。我们还将了解如何在 ASP.NET Core 中使用授权来提供对应用程序各种功能的访问。我们将用户凭据存储在 SQL Server 数据库中(注:您可以使用MySQL、PostgreSQL等其他关系型数据库),我们将使用 EF Core 框架和 Identity 框架进行数据库操作。

JSON Web Token (JWT) 是一个开放标准 (RFC 7519),它定义了一种紧凑且自包含的方式,使用JSON 对象用于在各方之间安全地传输信息。此信息可以验证和信任,因为它是数字签名的。JWT 可以使用密钥(使用HMAC算法)或使用RSAECDSA的公钥/私钥对进行签名。

在其紧凑的形式中,JSON Web Tokens 由三部分组成,由点 (.) 分隔,它们是:

  • Header(标题 )

  • Payload(有效载荷 )

  • Signature(签名 )

因此,JWT 格式通常如下所示:

xxxx.yyyy.zzzz

有关 JSON Web token的更多详细信息,请参阅下面的链接。

https://jwt.io/introduction/[2]

使用 Visual Studio 2022 创建 ASP.NET Core Web API

我们需要 Visual Studio 2022 来创建 .NET 6.0 应用程序。我们可以从 Visual Studio 2022 中选择 ASP.NET Core Web API 模板。

bc961391dac4cb8b9a4b59700cab15ab.png

我们可以为我们的项目起一个合适的名称并选择 .NET 6.0 框架。

7adb697a4a16783722b7a4e1dcf4cb86.png

我们的新项目将在随后创建。

我们必须将以下 4 个库安装到新项目中。

  • Microsoft.EntityFrameworkCore.SqlServer

  • Microsoft.EntityFrameworkCore.Tools

  • Microsoft.AspNetCore.Identity.EntityFrameworkCore

  • Microsoft.AspNetCore.Authentication.JwtBearer

您可以使用 NuGet 包管理器来安装上述包。

我们可以使用以下值更改 appsettings.json。它具有用于 JWT 身份认证的数据库连接详细信息和其他详细信息。

appsettings.json

{"Logging": {"LogLevel": {"Default": "Information","Microsoft.AspNetCore": "Warning"}},"AllowedHosts": "*","ConnectionStrings": {"ConnStr": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=JWTAuthDB;Integrated Security=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"},"JWT": {"ValidAudience": "http://localhost:4200","ValidIssuer": "http://localhost:5000","Secret": "JWTAuthenticationHIGHsecuredPasswordVVVp1OH7Xzyr"}
}

我们可以创建一个新文件夹 Auth 并在 Auth 文件夹下创建 ApplicationDbContext 类并添加以下代码。我们将在 Auth 文件夹下添加所有与身份认证相关的类。

ApplicationDbContext.cs

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;namespace IdentityDemo.Auth
{public class ApplicationDbContext : IdentityDbContext<IdentityUser>{public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options){}protected override void OnModelCreating(ModelBuilder builder){base.OnModelCreating(builder);}}
}

创建一个静态类 UserRoles 并添加以下值。

UserRoles.cs

namespace IdentityDemo.Auth
{public static class UserRoles{public const string Admin = "Admin";public const string User = "User";}
}

我们添加了两个常量值 AdminUser 作为角色。您可以根据需要添加任意数量的角色。

创建类 RegisterModel, 新用户注册时使用。

RegisterModel.cs

using System.ComponentModel.DataAnnotations;namespace IdentityDemo.Auth
{public class RegisterModel{[Required(ErrorMessage = "用户名不能为空")] public string? Username { get; set; }[EmailAddress][Required(ErrorMessage = "邮件不能为空")]public string? Email { get; set; }[Required(ErrorMessage = "密码不能为空")] public string? Password { get; set; }}
}

创建类 LoginModel 用于用户登录。

LoginModel.cs

using System.ComponentModel.DataAnnotations;namespace IdentityDemo.Auth
{public class LoginModel{[Required(ErrorMessage = "用户名不能为空")] public string? Username { get; set; }[Required(ErrorMessage = "密码不能为空")] public string? Password { get; set; }}
}

我们可以创建一个 Response 类,用于在用户注册和用户登录后返回响应值。如果请求失败,它也会返回错误消息。

Response.cs

namespace IdentityDemo.Auth
{public class Response{public string? Status { get; set; }public string? Message { get; set; }}
}

我们可以在 Controllers 文件夹中创建一个 API 控制器 AuthenticateController 并添加以下代码。

AuthenticateController.cs

using IdentityDemo.Auth;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;namespace IdentityDemo.Controllers
{[Route("api/[controller]")][ApiController]public class AuthenticateController : ControllerBase{private readonly UserManager<IdentityUser> _userManager;private readonly RoleManager<IdentityRole> _roleManager;private readonly IConfiguration _configuration;public AuthenticateController(UserManager<IdentityUser> userManager,RoleManager<IdentityRole> roleManager,IConfiguration configuration){_userManager = userManager;_roleManager = roleManager;_configuration = configuration;}[HttpPost][Route("login")]public async Task<IActionResult> Login([FromBody] LoginModel model){var user = await _userManager.FindByNameAsync(model.Username);if (user != null && await _userManager.CheckPasswordAsync(user, model.Password)){var userRoles = await _userManager.GetRolesAsync(user);var authClaims = new List<Claim>{new Claim(ClaimTypes.Name, user.UserName),new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),};foreach (var userRole in userRoles){authClaims.Add(new Claim(ClaimTypes.Role, userRole));}var token = GetToken(authClaims);return Ok(new{token = new JwtSecurityTokenHandler().WriteToken(token),expiration = token.ValidTo});}return Unauthorized();}[HttpPost][Route("register")]public async Task<IActionResult> Register([FromBody] RegisterModel model){var userExists = await _userManager.FindByNameAsync(model.Username);if (userExists != null)return StatusCode(StatusCodes.Status500InternalServerError,new Response { Status = "Error", Message = "用户已经存在!" });IdentityUser user = new(){Email = model.Email,SecurityStamp = Guid.NewGuid().ToString(),UserName = model.Username};var result = await _userManager.CreateAsync(user, model.Password);if (!result.Succeeded)return StatusCode(StatusCodes.Status500InternalServerError,new Response{Status = "Error", Message = "创建用户失败!请检查后再重试。"});return Ok(new Response { Status = "Success", Message = "新增用户成功!" });}[HttpPost][Route("register-admin")]public async Task<IActionResult> RegisterAdmin([FromBody] RegisterModel model){var userExists = await _userManager.FindByNameAsync(model.Username);if (userExists != null)return StatusCode(StatusCodes.Status500InternalServerError,new Response { Status = "Error", Message = "用户已经存在!" });IdentityUser user = new(){Email = model.Email,SecurityStamp = Guid.NewGuid().ToString(),UserName = model.Username};var result = await _userManager.CreateAsync(user, model.Password);if (!result.Succeeded)return StatusCode(StatusCodes.Status500InternalServerError,new Response{Status = "Error", Message = "创建用户失败!请检查后再重试。"});if (!await _roleManager.RoleExistsAsync(UserRoles.Admin))await _roleManager.CreateAsync(new IdentityRole(UserRoles.Admin));if (!await _roleManager.RoleExistsAsync(UserRoles.User))await _roleManager.CreateAsync(new IdentityRole(UserRoles.User));if (await _roleManager.RoleExistsAsync(UserRoles.Admin)){await _userManager.AddToRoleAsync(user, UserRoles.Admin);}if (await _roleManager.RoleExistsAsync(UserRoles.Admin)){await _userManager.AddToRoleAsync(user, UserRoles.User);}return Ok(new Response { Status = "Success", Message = "新增用户成功!" });}private JwtSecurityToken GetToken(List<Claim> authClaims){var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWT:Secret"]));var token = new JwtSecurityToken(issuer: _configuration["JWT:ValidIssuer"],audience: _configuration["JWT:ValidAudience"],expires: DateTime.Now.AddHours(3),claims: authClaims,signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256));return token;}}
}

我们在控制器类中添加了三个方法 loginregisterregister-admin``。registerregister-admin 几乎相同,但 register-admin 方法将用于创建具有 Admin 角色的用户。在 login 方法中,我们在成功登录后返回了一个 JWT token

.NET 6.0 中,微软删除了 Startup 类(注:您可以恢复继续使用这种方式),只保留了 Program 类。我们必须在 Program 类中定义所有依赖注入和其他配置。

Program.cs

using IdentityDemo.Auth;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using System.Text;var builder = WebApplication.CreateBuilder(args);
ConfigurationManager configuration = builder.Configuration;// Add services to the container.// For Entity Framework
builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(configuration.GetConnectionString("ConnStr")));// For Identity
builder.Services.AddIdentity<IdentityUser, IdentityRole>().AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();// Adding Authentication
builder.Services.AddAuthentication(options =>
{options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})// Adding Jwt Bearer
.AddJwtBearer(options =>
{options.SaveToken = true;options.RequireHttpsMetadata = false;options.TokenValidationParameters = new TokenValidationParameters(){ValidateIssuer = true,ValidateAudience = true,ValidAudience = configuration["JWT:ValidAudience"],ValidIssuer = configuration["JWT:ValidIssuer"],IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JWT:Secret"]))};
});builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();var app = builder.Build();// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{app.UseSwagger();app.UseSwaggerUI();
}app.UseHttpsRedirection();// Authentication & Authorization
app.UseAuthentication();
app.UseAuthorization();app.MapControllers();app.Run();

我们必须在运行应用程序之前创建所需的数据库和表。由于我们使用的是实体框架(EF Core),我们可以使用下面的数据库迁移命令和包管理器控制台来创建一个迁移脚本:

add-migration Initial

5e1a44390a52469d51b4addf2f097926.png

使用以下命令创建数据库和表:

update-database

ea3c42f0e6df2fe0ffc760eaa636aaff.png

如果您使用 SQL Server 对象资源管理器检查数据库,您可以看到在数据库内部创建了以下数据库表:

57bbd81e46be058cc701d154d2da5055.png

在数据库迁移过程中,为 UserRoleClaims 创建了 7 张表。这是用于 Identity框架。

ASP.NET Core Identity 是一个会员系统,允许您向应用程序添加登录功能。用户可以创建帐户并使用用户名和密码登录,也可以使用外部登录提供程序,例如 Facebook、Google、Microsoft Account、Twitter 等。

您可以将 ASP.NET Core Identity 配置为使用 SQL Server 数据库来存储用户名、密码和配置文件数据。或者,你可以使用自己的持久化存储将数据存储在另一个其他持久化存储中,例如 Azure 表存储。

我们可以在 WeatherForecast 控制器中添加 Authorize 属性。

ba4813d89b33077d4a1cf0372bef5b82.png

我们可以运行应用程序并尝试从 Postman 工具访问WeatherForecastController中的 get 方法。

fb4579b75f27c9aaec7139a7a6e5b092.png

我们收到了 401 未经授权的错误。因为,我们已经为整个控制器添加了 Authorize 属性。我们必须通过请求头提供一个有效的token来访问这个控制器和控制器内的方法。

我们可以在AuthenticateController中使用注册方法创建一个新用户。

2e4d4afd3b3f3588413503143a88ba69.png

我们以原始 JSON 格式提供了输入数据。

我们可以使用上述用户凭据登录并获取有效的 JWT token

e572ea70143336de777f239cd436ba04.png

使用上述凭据成功登录后,我们收到了一个token

我们可以使用https://jwt.io[3]站点 解码token并查看声明和其他信息。

7b2dc7d9fba0d4e3b994117f1a0f26dd.png

我们可以在授权选项卡中将上述token值作为Bearer token传递,并再次调用WeatherForecastControllerget 方法。

755a11582653962a3880b088634cc093.png

这一次,我们成功地接收到了来自控制器的值。

我们可以使用基于角色的授权来更改WeatherForecastController

e00661373377ea8b3e14a2f193365d97.png

现在,只有具有管理员(Admin)角色的用户才能访问此控制器和方法。

我们可以尝试在 Postman 工具中再次使用相同的token访问WeatherForecastController。

34848ce34189851d216824237e907422.png

我们现在收到了 403 拒绝错误而不是 401。即使我们传递了一个有效的token,我们也没有足够的权限来访问控制器。要访问此控制器,用户必须具有Admin角色权限。当前用户是普通用户,没有任何Admin角色权限。

我们可以创建一个具有Admin角色权限的新用户。我们已经在AuthenticateController中有一个方法register-admin用于相同的目的。

ba5cdbb8ecf87701fed39c4abafcb4d7.png

我们可以使用这些新的用户凭据登录并获得一个新的token。如果您对token进行解码,您可以看到角色已添加到token中。

39239632a156392704fa0cfded9c67fe.png

我们可以使用这个token而不是旧token来访问WeatherForecastController

0f5d7e093f07c112aa661f6e2a2e9fb9.png

现在我们已经成功地从WeatherForecastController中获取了数据。

结论

在这篇文章中,我们了解了如何在 .NET 6.0 ASP.NET Core Web API 应用程序中创建 JSON Web token,并使用此token进行身份认证和授权。我们创建了两个用户,一个没有任何角色,一个有Admin角色。我们在控制器级别应用了身份认证和授权,并看到了这两个用户的不同行为。

  • 本文源码:IdentityDemo[4]

参考资料

[1]

C# Corner: https://www.c-sharpcorner.com/

[2]

https://jwt.io/introduction/: https://jwt.io/introduction/

[3]

https://jwt.io: https://jwt.io

[4]

IdentityDemo: https://github.com/dotnet9/ASP.NET-Core-Test/tree/master/src/IdentityDem

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

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

相关文章

adb devices 里面有很多 emulator-XXXX的解决方法

2019独角兽企业重金招聘Python工程师标准>>> adb kill-server 转载于:https://my.oschina.net/sfshine/blog/700354

MQ(Message Queue)简介

一、何为MQ&#xff1f; MQ全称为Message Queue, 消息队列&#xff08;MQ&#xff09;是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息&#xff08;针对应用程序的数据&#xff09;来通信&#xff0c;而无需专用连接来链接它们。消息传递指的是程序之间通…

Blazor University (39)JavaScript 互操作 —— 更新 document title

原文链接&#xff1a;https://blazor-university.com/javascript-interop/calling-javascript-from-dotnet/updating-the-document-title/更新 document title源代码[1]在创建 Blazor 布局[2]部分中&#xff0c;我们看到了 Blazor 应用程序如何存在于 HTML&#xff08;或 cshtm…

IIS 日志文件位置

IIS 6 Log files location IIS 6中日志文件的位置%windir%\System32\LogFilesIIS 7 Log files location IIS的日志文件的位置%SystemDrive%\inetpub\logs\LogFiles用户每打开一次网页&#xff0c;iis 都会记录用户IP、访问的网页地址、访问时间、访问状态等信息&#xff0c;这些…

APP测试流程和测试点

1 APP测试基本流程 1.1流程图 1.2测试周期 测试周期可按项目的开发周期来确定测试时间&#xff0c;一般测试时间为两三周&#xff08;即15个工作日&#xff09;&#xff0c;根据项目情况以及版本质量可适当缩短或延长测试时间。正式测试前先向主管确认项目排期。 1.3测试资源 测…

39所强基计划试点高校已全部公布招生简章

截至目前(4月8日下午) 39所强基计划试点高校 已全部公布招生简章 各高校招生要求是什么&#xff1f; 招生专业有哪些&#xff1f; 什么时候报名&#xff1f; 一起来看 北京大学 招生对象及报名条件 各省&#xff08;区、市&#xff09;符合2022年全国普通高等学校招生统…

【ArcGIS错误异常100问】之001:License服务无法启动权威解决办法

测试环境&#xff1a; 操作系统&#xff1a;Windows10ArcGIS版本&#xff1a;10.X结果&#xff1a;通过测试 文章目录1. 错误提示2. 问题分析3. 解决办法3.1 关闭Windows Defender3.2 关闭系统防火墙3.3 删除迈克菲&#xff08;McAfee&#xff09;杀毒软件3.4 在系统服务中启动…

ASP.NET Core 技术内幕与项目实战读后感

前几天拿到了杨中科老师的新书《ASP.NET Core 技术内幕与项目实战》&#xff0c;迫不及待的“两”口气读完了。用一句话来总结&#xff0c;这是一本写给.NET开发者的非常实用的接地气的好书&#xff0c;感觉有必要自发为这本书宣传一波。杨老师在 .NET 开发者社区中的知名度非常…

avalon2学习教程15指令总结

avalon的指令在上一节已经全部介绍完毕&#xff0c;当然有的语焉不详&#xff0c;如ms-js。本节主要总结我对这方面的思考与探索。 MVVM的成功很大一语分是来自于其指令&#xff0c;或叫绑定。让操作视图的功能交由形形式式的指令来代劳。VM&#xff0c;成了一个大管家。它只一…

【ArcGIS错误异常100问】之002:Error 000735 简化容差:值是必需的(简化线、简化面工具)

测试环境&#xff1a; 操作系统&#xff1a;windows7ArcGIS版本&#xff1a;10.2结果&#xff1a;通过测试 文章目录1. 错误提示2. 问题分析3. 解决办法4. 问题验证1. 错误提示 在ArcGIS中使用简化面或者简化线工具时&#xff0c;点击确定会提示Error 000735&#xff1a;简化容…

.NET桌面开发的一些思考

在22日&#xff0c;我在公众号上发布了一条短文字&#xff0c;内容如下&#xff1a;其实在.NET开发大军中&#xff0c;还有一股力量&#xff0c;那就是桌面程序的开发者们。他们很少发声&#xff0c;可能技术成熟&#xff0c;可能太企业化了&#xff0c;也可能我没关注到。最近…

【ArcGIS错误异常100问】之003:属性表中文乱码解决办法总结

测试环境&#xff1a; 操作系统&#xff1a;windows7ArcGIS版本&#xff1a;10.X、Pro结果&#xff1a;通过测试 文章目录1. 错误提示2. 原因分析3. 解决方法4. 问题验证1. 错误提示 如图所示&#xff0c;安装完ArcGIS Pro后&#xff0c;由于计算机系统和应用软件字符编码的问…

大型网站架构演化(二)——应用服务和数据服务分离

随着网站业务的发展&#xff0c;一台服务器逐渐不能满足需求&#xff1a;越来越多的用户访问导致性能越来越差&#xff0c;越来越多的数据导致存储空间不足。这时就需要将应用和数据分离。应用和数据分离后整个网站使用三台服务器&#xff1a;应用服务器、文件服务器和数据库服…

再不自动化就晚啦!优云教你4步打造基于CentOS的产品镜像

随着Linux程序的增多&#xff0c;软件的安装过程中经常出现如下问题&#xff1a; 1、硬件配置类似或者相同时&#xff0c;批量安装系统和软件&#xff0c;希望实现自动化安装&#xff0c;减少安装时间和人为出错。 2、工程实施人员在不同客户现场进行系统和软件安装(硬件配置不…

【ArcGIS错误异常100问】之004:ArcGIS表转Excel超了65535限制解决办法

测试环境&#xff1a; 操作系统&#xff1a;windows7ArcGIS版本&#xff1a;10.2 文章目录1. 错误提示2. 原因分析3. 解决方法1. 错误提示 如下图&#xff0c;当矢量shp图斑数目过多&#xff0c;文件超过了65535条记录时&#xff0c;利用ArcGIS的表转Excel工具处理成Excel文件…

[转]硬核 | Redis 布隆(Bloom Filter)过滤器原理与实战

在Redis 缓存击穿&#xff08;失效&#xff09;、缓存穿透、缓存雪崩怎么解决&#xff1f;中我们说到可以使用布隆过滤器避免「缓存穿透」。 码哥&#xff0c;布隆过滤器还能在哪些场景使用呀&#xff1f; 比如我们使用「码哥跳动」开发的「明日头条」APP 看新闻&#xff0c;如…

Senparc.Weixin.Sample.MP源码剖析

Senparc.Weixin.Sample.MP是微信公众号样例的.NET6源码&#xff0c;项目配置文件appsettings.json的修改和微信公众号测试环境的搭建参考&#xff1a;微信公众号调试与Natapp环境搭建。接下来从项目结构&#xff0c;项目应用和项目源码3个角度来进行讲解。一.项目结构角度项目代…

mock.js使用

一、Mock.js入门 1&#xff0e; 什么是mock.js? Mock.js &#xff08;官网http://mockjs.com/&#xff09;是一款模拟数据生成器&#xff0c;旨在帮助前端攻城狮独立 于后端进行开发&#xff0c;帮助编写单元测试。提供了以下模拟功能&#xff1a; 1,根据数据模板生成模拟数据…

【ArcGIS微课1000例】0042:ArcGIS自带取色器工具的妙用

在ArcGIS中作图时,通常要进行颜色对照填充,输入特定的RGB值,本文介绍ArcGIS自带取色器工具的妙用,及第三方颜色拾取工具。 文章目录 一、ArcGIS自带取色器二、第三方取色器工具一、ArcGIS自带取色器 很多人可能不知道,ArcGIS中自带取色器工具,如下图所示。 当然了,自带…

微信.NET SDK-Senparc资料整理

微信生态系统包括微信公众号、小程序、微信支付、微信开放平台、企业微信、小游戏等&#xff0c;官方提供了很多的API接口。Senparc是目前使用最广泛的微信.NET SDK&#xff0c;同时支持支持.NET Framework 4.5/.NET Core 2.x/.NET Core 3.x/.NET 5/.NET 6。由于在微信生态开发…