|
翻译自 Mohamad Lawand 2021年1月22日的文章 《Asp Net Core 5 Rest API Authentication with JWT Step by Step》 在本文中,我将向您展示如何向我们的 http://Asp.Net Core REST API 添加 JWT 身份验证。
我们将介绍的主题包含注册、登录功能以及如何使用 JWT (Json Web Tokens)和 Bearer 身份验证。
你也可以在 YouTube 上观看完整的视频,还可以下载源代码。
这是 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(&#34;JwtConfig&#34;));
将这些配置添加到我们的 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[&#34;JwtConfig:Secret&#34;]);
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 &#34;Adding authentication to our Api&#34;
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(&#34;api/[controller]&#34;)] // 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(&#34;Register&#34;)]
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>()
{
&#34;Email already in use&#34;
},
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>()
{
&#34;Invalid payload&#34;
},
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(&#34;Id&#34;, 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(&#34;Login&#34;)]
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>()
{
&#34;Invalid login request&#34;
},
Success = false
});
}
// 现在我们需要检查用户是否输入了正确的密码
var isCorrect = await _userManager.CheckPasswordAsync(existingUser, user.Password);
if(!isCorrect)
{
// 出于安全原因,我们不想透露太多关于请求失败的信息
return BadRequest(new RegistrationResponse()
{
Errors = new List<string>()
{
&#34;Invalid login request&#34;
},
Success = false
});
}
var jwtToken = GenerateJwtToken(existingUser);
return Ok(new RegistrationResponse()
{
Success = true,
Token = jwtToken
});
}
return BadRequest(new RegistrationResponse()
{
Errors = new List<string>()
{
&#34;Invalid payload&#34;
},
Success = false
});
}
现在,我们可以在 Postman 中对其进行测试,我们将会看到 JWT token 已经成功生成。
下一步是保护我们的控制器,需要做的就是向控制器添加 Authorize 属性。
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Route(&#34;api/[controller]&#34;)] // api/todo
[ApiController]
public class TodoController : ControllerBase
此时,如果我们再对 Todo 进行测试,则由于未获得授权,我们将会无法执行任何请求。为了发送带授权的请求,我们需要添加带有 Bearer token 的授权 Header,以便 http://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
译者 : 技术译民
出品 : 技术译站
链接 : 英文原文
|
|