翻译自 Mohamad Lawand 2021年1月22日的文章 《Asp Net Core 5 Rest API Authentication with JWT Step by Step》 1
在本文中,我将向您展示如何向我们的 Asp.Net Core REST API 添加 JWT 身份验证。
我们将介绍的主题包含注册、登录功能以及如何使用 JWT (Json Web Tokens)2和 Bearer 身份验证。
你也可以在 YouTube 上观看完整的视频3,还可以下载源代码4。
这是 API 开发系列的第二部分,本系列还包含:
Part 1:Asp.Net Core 5 REST API - Step by Step Part 3:Asp Net Core 5 REST API 中使用 RefreshToken 刷新 JWT - Step by Step我们将基于上一篇文章中创建的 Todo REST API 应用程序进行当前的讲述,您可以通过阅读上一篇文章并与我一起构建应用程序,或者可以从 github 下载上一篇中的源代码。
前一篇文章中的代码准备好以后,就让我们开始本文吧。
首先,我们需要安装一些依赖包以使用身份验证:
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore dotnet add package Microsoft.AspNetCore.Identity.UI
然后,我们需要更新 appsettings.json,在 appsettings 中添加 JWT 的设置部分,在该设置中添加一个 JWT secret(密钥)。
"JwtConfig": { "Secret" : "ijurkbdlhmklqacwqzdxmkkhvqowlyqa" },
为了生成 secret,我们可以使用一个免费的 Web 工具(https://www.browserling.com/tools/random-string)来生成一个随机的 32 个字符的字符串。
我们在 appsettings 中添加完随机生成的 32 个字符的字符串后,接着需要在根目录中创建一个名为 Configuration 的新文件夹。
在这个 Configuration 文件夹中,我们将创建一个名为
JwtConfig的新类:
public class JwtConfig { public string Secret { get; set; } }
现在我们需要更新
Startup类,在
ConfigureServices方法内,我们需要添加以下内容,以便将 JWT 配置注入到应用程序中:
services.Configure<JwtConfig>(Configuration.GetSection("JwtConfig"));
将这些配置添加到我们的
Startup类中,即可在 Asp.Net Core 中间件和 IoC 容器中注册配置。
下一步是在我们的
Startup类中添加和配置身份验证,在我们的
ConfigureServices方法中,我们需要添加以下内容:
// 在本段中,我们将配置身份验证并设置默认方案 services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(jwt => { var key = Encoding.ASCII.GetBytes(Configuration["JwtConfig:Secret"]); jwt.SaveToken = true; jwt.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, //这将使用我们在 appsettings 中添加的 secret 来验证 JWT token 的第三部分,并验证 JWT token 是由我们生成的 IssuerSigningKey = new SymmetricSecurityKey(key), //将密钥添加到我们的 JWT 加密算法中 ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = true, RequireExpirationTime = false }; }); services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores<ApiDbContext>();
更新好
ConfigureServices之后,我们需要更新
Configure方法,添加身份验证:
app.UseAuthentication();
配置添加完成后,我们需要构建应用程序,检查是否所有的内容都可以正常构建:
dotnet build dotnet run
下一步是更新我们的
ApiDbContext,以便使用 Asp.Net 为我们提供的身份提供程序,导航到 Data 文件夹中的
ApiDbContext,然后按以下内容更新
ApiDbContext类:
public class ApiDbContext : IdentityDbContext
通过从
IdentityDbContext而不是
DbContext继承,EntityFramework 将知道我们正在使用身份验证,并且将为我们构建基础设施以使用默认身份表。
要在我们的数据库中生成身份表,我们需要准备迁移脚本并运行它们。也就是说,我们需要在终端中输入并运行以下命令:
dotnet ef migrations add "Adding authentication to our Api" dotnet ef database update
迁移完成后,我们可以使用 Dbeaver 打开数据库 app.db,我们可以看到 EntityFramework 已经为我们创建了身份表。
下一步是设置控制器并为用户构建注册流程。我们需要在 Controllers 文件夹中创建一个新的控制器,并创建对应的 DTO 类(Data Transfer Objects)。
先在根目录中的 Configuration 文件夹中添加一个名为
AuthResult的类:
// Configuration\AuthResult.cs public class AuthResult { public string Token { get; set; } public bool Success { get; set; } public List<string> Errors { get; set; } }
然后我将添加一些文件夹来组织 DTOs,在 Models 文件夹中添加一个名为 DTOs 的文件夹,然后在此文件夹中创建两个子文件夹 Requests 和 Responses。
我们需要添加供我们在控制器中的注册 Action 使用的
UserRegistrationDto。导航到 Models/DTO/Requests,添加一个新类
UserRegistrationDto。
// Models\DTOs\Requests\UserRegistrationDto.cs public class UserRegistrationDto { [Required] public string Username { get; set; } [Required] [EmailAddress] public string Email { get; set; } [Required] public string Password { get; set; } }
添加
RegistrationResponse响应类。
// Models\DTOs\Responses\RegistrationResponse.cs public class RegistrationResponse : AuthResult { }
现在,我们需要添加用户注册控制器,在控制器文件夹中添加一个新类,命名为
AuthManagementController,并使用以下代码更新它:
// Controllers\AuthManagementController.cs [Route("api/[controller]")] // api/authmanagement [ApiController] public class AuthManagementController : ControllerBase { private readonly UserManager<IdentityUser> _userManager; private readonly JwtConfig _jwtConfig; public AuthManagementController( UserManager<IdentityUser> userManager, IOptionsMonitor<JwtConfig> optionsMonitor) { _userManager = userManager; _jwtConfig = optionsMonitor.CurrentValue; } [HttpPost] [Route("Register")] public async Task<IActionResult> Register([FromBody] UserRegistrationDto user) { // 检查传入请求是否有效 if(ModelState.IsValid) { // 检查使用相同电子邮箱的用户是否存在 var existingUser = await _userManager.FindByEmailAsync(user.Email); if(existingUser != null) { return BadRequest(new RegistrationResponse() { Errors = new List<string>() { "Email already in use" }, Success = false }); } var newUser = new IdentityUser() { Email = user.Email, UserName = user.Username }; var isCreated = await _userManager.CreateAsync(newUser, user.Password); if(isCreated.Succeeded) { var jwtToken = GenerateJwtToken( newUser); return Ok(new RegistrationResponse() { Success = true, Token = jwtToken }); } else { return BadRequest(new RegistrationResponse() { Errors = isCreated.Errors.Select(x => x.Description).ToList(), Success = false }); } } return BadRequest(new RegistrationResponse() { Errors = new List<string>() { "Invalid payload" }, Success = false }); } private string GenerateJwtToken(IdentityUser user) { //现在,是时候定义 jwt token 了,它将负责创建我们的 tokens var jwtTokenHandler = new JwtSecurityTokenHandler(); // 从 appsettings 中获得我们的 secret var key = Encoding.ASCII.GetBytes(_jwtConfig.Secret); // 定义我们的 token descriptor // 我们需要使用 claims (token 中的属性)给出关于 token 的信息,它们属于特定的用户, // 因此,可以包含用户的 Id、名字、邮箱等。 // 好消息是,这些信息由我们的服务器和 Identity framework 生成,它们是有效且可信的。 var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(new [] { new Claim("Id", user.Id), new Claim(JwtRegisteredClaimNames.Email, user.Email), new Claim(JwtRegisteredClaimNames.Sub, user.Email), // Jti 用于刷新 token,我们将在下一篇中讲到 new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) }), // token 的过期时间需要缩短,并利用 refresh token 来保持用户的登录状态, // 不过由于这只是一个演示应用,我们可以对其进行延长以适应我们当前的需求 Expires = DateTime.UtcNow.AddHours(6), // 这里我们添加了加密算法信息,用于加密我们的 token SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; var token = jwtTokenHandler.CreateToken(tokenDescriptor); var jwtToken = jwtTokenHandler.WriteToken(token); return jwtToken; } }
添加完注册的 Action 后,我们可以在 Postman 中对其进行测试并获得 JWT token。
接下来是创建用户登录请求:
// Models\DTOs\Requests\UserLoginRequest.cs public class UserLoginRequest { [Required] [EmailAddress] public string Email { get; set; } [Required] public string Password { get; set; } }
然后,我们需要在
AuthManagementController中添加
Login方法:
[HttpPost] [Route("Login")] public async Task<IActionResult> Login([FromBody] UserLoginRequest user) { if(ModelState.IsValid) { // 检查使用相同电子邮箱的用户是否存在 var existingUser = await _userManager.FindByEmailAsync(user.Email); if(existingUser == null) { // 出于安全原因,我们不想透露太多关于请求失败的信息 return BadRequest(new RegistrationResponse() { Errors = new List<string>() { "Invalid login request" }, Success = false }); } // 现在我们需要检查用户是否输入了正确的密码 var isCorrect = await _userManager.CheckPasswordAsync(existingUser, user.Password); if(!isCorrect) { // 出于安全原因,我们不想透露太多关于请求失败的信息 return BadRequest(new RegistrationResponse() { Errors = new List<string>() { "Invalid login request" }, Success = false }); } var jwtToken = GenerateJwtToken(existingUser); return Ok(new RegistrationResponse() { Success = true, Token = jwtToken }); } return BadRequest(new RegistrationResponse() { Errors = new List<string>() { "Invalid payload" }, Success = false }); }
现在,我们可以在 Postman 中对其进行测试,我们将会看到 JWT token 已经成功生成。
下一步是保护我们的控制器,需要做的就是向控制器添加
Authorize属性。
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [Route("api/[controller]")] // api/todo [ApiController] public class TodoController : ControllerBase
此时,如果我们再对
Todo进行测试,则由于未获得授权,我们将会无法执行任何请求。为了发送带授权的请求,我们需要添加带有 Bearer token 的授权 Header,以便 Asp.Net 可以验证它,并授予我们执行操作的权限。
译者注:
添加 Bearer token 请求头的方法是:在 Headers 中,添加一个名称为
Authorization的 Header 项,值为
Bearer <token>(需将
<token>替换为真实的 token 值)。使用 Postman 测试时,可参考 Postman 官方文档: https://learning.postman.com/docs/sending-requests/authorization/#bearer-token。
至此,我们已经完成了使用 JWT 为 REST API 添加身份验证的功能。
感谢您花时间阅读本文。
本文是 API 开发系列的第二部分,本系列还包含:
Part 1:Asp.Net Core 5 REST API - Step by Step Part 3:Asp Net Core 5 REST API 中使用 RefreshToken 刷新 JWT - Step by Step作者 : Mohamad Lawand
译者 : 技术译民
出品 : 技术译站
链接 : 英文原文
https://dev.to/moe23/asp-net-core-5-rest-api-authentication-with-jwt-step-by-step-140d Asp Net Core 5 Rest API Authentication with JWT Step by Step↩
https://mp.weixin.qq.com/s/jnC8FDKm0Srj0ww-EvdUiw JWT 介绍 - Step by Step↩
https://youtu.be/LgpC4tYtc6Y↩
https://github.com/mohamadlawand087/v7-RestApiNetCoreAuthentication↩