介绍
在本文中,我们使用JWT 身份验证在 ASP.NET Core 中构建安全的 REST API 。我们从 JWT 的本质及其结构开始。
文章的第 1 - 4 节 解释了什么是 JWT 令牌、如何使用 .Net Core 设置它、安装所需的包、创建应用程序模型、迁移和更新数据库
第 5 - 9 节 重点介绍生成安全 JWT 令牌、使用和不使用生成的令牌进行安全调用以及注册用户。已使用 Postman 测试和触发 Web 请求以保护 API。
- JWT 结构
请参阅下面的 JWT 令牌。它在下面被分解和解释为标题、有效载荷、签名,
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1Njc3OCIsIm5hbWUiOiJSYWp1IEt1bWFyIiwiaWF0IjozNDU2Njd9.eJBP0IBy20JT9iwP6pHiKkFfHcbMPg_gVYKH-e5j0qk
标题
提供有关令牌 (JWT) 类型和用于签署令牌的算法(例如 RSA、SHA256)的详细信息。在上面的例子中,它是,
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
有效载荷
包含用户详细信息,也称为声明,这是要保护的数据。在上面的例子中,它是,
eyJzdWIiOiI1Njc3OCIsIm5hbWUiOiJSYWp1IEt1bWFyIiwiaWF0IjozNDU2Njd9
签名
标头、有效载荷和密钥之间的加密。在上面的例子中,它是,
eJBP0IBy20JT9iwP6pHiKkFfHcbMPg_gVYKH-e5j0qk
- 让我们从安装必需的包开始
使用 API 模板创建一个新的 ASP.NET Core 应用程序并安装以下包。目标框架是3.1及以上。
Install-Package Microsoft.AspNetCore.Authentication.JwtBearer Install-Package Microsoft.AspNetCore.Identity.EntityframeworkCore Install-Package Microsoft.EntityframeworkCore Install-Package Microsoft.EntityframeworkCore.Design Install-Package Microsoft.EntityframeworkCore.SqlServer Install-Package Microsoft.EntityframeworkCore.Tools Install-Package Microsoft.VisualStudio.Web.CodeGeneration.Design Install-Package System.IdentityModel.Tokens.Jwt
3(一)。如图所示在代码中配置JWT
将以下内容添加到 appsettings.json,
"JWT": {
"key": "C1CF4B7DC4C4175B6618DE4F55CA4",
"Issuer": "MySecureRestApi",
"Audience": "AsecureRestApiUser",
"DurationInMinutes": 20
}
为上述设置创建一个相应的类,即 Settings/JWT.cs ,它将用于使用 ASP.NET Core 的 IOptions 功能从我们之前创建的 appsettings.json 的 JWT 部分读取数据。
public class JWTSettings {
public string Key {
get;
set;
}
public string Issuer {
get;
set;
}
public string Audience {
get;
set;
}
public double DurationInMinutes {
get;
set;
}
}
然后,将以下类添加到您的项目中:
DbContext - 在 appsettings.json 中添加连接字符串
源自 IdentityUser 的 ApplicationUser
3(b)。如图所示在代码中配置JWT
要配置身份验证,请将代码添加到 ConfigureServices 方法,如下所示 -
public void ConfigureServices(IServiceCollection services) {
Line 1 //The JWT Configuration from AppSettings
services.Configure < JWTSettings > (_configuration.GetSection("JWT"));
//The User Manager Service
services.AddIdentity < ApplicationUser, IdentityRole > ().AddEntityframeworkStores < ApplicationDbContext > ();
services.AddScoped < IUserService, UserService > ();
//Adding DB Context with MSSQL
services.AddDbContext < ApplicationDbContext > (options => options.UseSqlServer(_configuration.GetConnectionString("DefaultConnection"), b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)));
//The JWT Athentication
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(oa => {
oa.RequireHttpsmetadata = false;
oa.SaveToken = false;
oa.TokenValidationParameters = new TokenValidationParameters {
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
ValidIssuer = _configuration["JWT:Issuer"],
ValidAudience = _configuration["JWT:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWT:Key"]))
};
});
services.AddControllers();
}
4.对于数据库迁移
在 APP Settings.json 中添加连接字符串。对于创建数据库,使用 Entity framework Core 的代码优先方法。
在执行应用程序之前,请在包管理器控制台上运行以下命令以应用迁移。
add-migration “migrationDB”
update-database
在成功迁移后创建一组 ASPNET 用户和角色表,见下文,
- 注册用户
创建 具有以下属性的 Models/RegisterModel.cs。用户必须使用此对象发布数据才能注册。
public class RegisterModel {
[Required]
public string FirstName {
get;
set;
}
[Required]
public string LastName {
get;
set;
}
[Required]
public string Username {
get;
set;
}
[Required]
public string Email {
get;
set;
}
[Required]
public string Password {
get;
set;
}
}
在 IUserService.cs 中,将以下 2 个函数定义添加到接受注册模型的注册用户。
TaskRegisterAsync(RegisterModel model); Task GetTokenAsync(TokenRequestModel model);
转到具体类 UserService 来实现注册功能。
public async Task < string > RegisterAsync(RegisterModel model) {
var user = new ApplicationUser {
UserName = model.Username,
Email = model.Email,
FirstName = model.FirstName,
LastName = model.LastName
};
var userWithSameEmail = await _userManager.FindByEmailAsync(model.Email);
if (userWithSameEmail == null) {
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded) {
return $ "Success: User Registered with username {user.UserName}";
} else {
string descr = "";
if (result.Errors.Any()) {
foreach(var item in result.Errors) {
descr += item.Description;
}
}
return $ "Error(s), registering user : {descr}";
}
} else {
return $ "Error(s), Email {user.Email } is already registered.";
}
}
在上面的代码片段中,我们接受 RegisterModel 对象,执行验证并在 DB 中创建用户,否则返回相应的错误消息。
从控制器,呼叫将如下所示,
[HttpPost("CreateUser")]
public async Task < ActionResult > RegisterAsync(RegisterModel model) {
var result = await _userService.RegisterAsync(model);
return Ok(result);
}
- 用 Postman 测试
打开 Postman 并定义一个要发布到/api/user/CreateUser的原始 JSON 对象 。检查以下屏幕截图,成功后,我们会收到一条确认的用户创建消息。
7.生成JWT令牌
让我们尝试获取 JWT 令牌。我们将构建一个令牌生成函数,它接受一个 TokenRequestModel(电子邮件、密码),验证它们,并为我们构建一个令牌。
以下是令牌Models/TokenRequestModel.cs和Models/AuthenticationModel.cs 的模型类 ,
public class TokenRequestModel {
[Required]
public string Email {
get;
set;
}
[Required]
public string Password {
get;
set;
}
}
另一个类 AuthenticationModel.cs 基本上是来自 API 端点的响应。此端点将返回状态消息、用户详细信息,最后返回我们的令牌。
public class AuthenticationModel {
public string Message {
get;
set;
}
public bool IsAuthenticated {
get;
set;
}
public string UserName {
get;
set;
}
public string Email {
get;
set;
}
public string Token {
get;
set;
}
}
在 UserService 的具体类中检查以下实现以生成令牌,
public async Task < AuthenticationModel > GetTokenAsync(TokenRequestModel model) {
var authenticationModel = new AuthenticationModel();
var user = await _userManager.FindByEmailAsync(model.Email);
if (user == null) {
authenticationModel.IsAuthenticated = false;
authenticationModel.Message = $ "No Accounts Registered with {model.Email}.";
return authenticationModel;
}
if (await _userManager.CheckPasswordAsync(user, model.Password)) {
authenticationModel.IsAuthenticated = true;
JwtSecurityToken jwtSecurityToken = await CreateJwtToken(user);
authenticationModel.Token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
authenticationModel.Email = user.Email;
authenticationModel.UserName = user.UserName;
return authenticationModel;
}
authenticationModel.IsAuthenticated = false;
authenticationModel.Message = $ "Incorrect Credentials for user {user.Email}.";
return authenticationModel;
}
private async Task < JwtSecurityToken > CreateJwtToken(ApplicationUser user) {
var userClaims = await _userManager.GetClaimsAsync(user);
var roles = await _userManager.GetRolesAsync(user);
var roleClaims = new List < Claim > ();
for (int i = 0; i < roles.Count; i++) {
roleClaims.Add(new Claim("roles", roles[i]));
}
var claims = new [] {
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim("uid", user.Id)
}.Union(userClaims).Union(roleClaims);
var symmetricSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwt.Key));
var signingCredentials = new SigningCredentials(symmetricSecurityKey, SecurityAlgorithms.HmacSha256);
var jwtSecurityToken = new JwtSecurityToken(issuer: _jwt.Issuer, audience: _jwt.Audience, claims: claims, expires: DateTime.UtcNow.AddMinutes(_jwt.DurationInMinutes), signingCredentials: signingCredentials);
return jwtSecurityToken;
}
以下是上述代码功能中的活动,
第 4 行 到第 11 行 检查提供的电子邮件和密码的有效性,如果不是,则返回一条消息。
第 14 行 调用 CreateJWTToken 函数。此函数构建 JWT。它获取用户的所有声明(用户详细信息)以及角色(如果有)。
最后, 第 51 行到第 57 行 创建一个新的 JWT 安全令牌并返回它们。
现在,将此函数连接到Controllers/UserController.cs
[HttpPost("GenerateToken")]
public async Task < IActionResult > GetTokenAsync(TokenRequestModel model) {
var result = await _userService.GetTokenAsync(model);
if (string.IsNullOrEmpty(result.Token)) return NotFound(" " + result.Message);
return Ok(result.Token);
}
接下来,将有效的电子邮件和密码请求发送到 …/api/user/GenerateToken。有关 JWT 令牌和包含用户详细信息的 AuthenticationModel 对象的成功响应,请参见下面的 Postman 屏幕截图。此令牌的有效期为 20 分钟(因为 app.json 中的过期时间设置为 20 分钟)。
在为同一用户发布无效密码时,PostMan 返回以下消息:“用户凭据不正确:johnk@gmail.com”。
- 安全地访问数据库信息,即使用生成的令牌。
我之前在我的数据库中创建了几个城市,我将使用此不记名令牌访问这些城市。为此,我需要在 Controller 类中使用 [Authorize] 关键字装饰 Get City 端点。参考下面的代码库,
[Authorize]
[HttpGet]
[Route("AllCities")]
public IActionResult GetAllCities(string Email) {
if (appdbcontext.CountryInfos == null || !appdbcontext.CountryInfos.Any()) return Ok("No Country-Cities created as yet.");
var res = appdbcontext.CountryInfos.Where(aa => aa.Email == Email).
Select(xx => new {
City = xx.City,
Email = xx.Email,
Isfavourite = xx.Isfavourite
});
if (res.Count() < 1) {
return NotFound("No records against this UserEmail" + Email);
} else return Ok(res);
}
现在复制上一个片段中的承载令牌并在 POSTMAN 中触发此 GET 请求,…api/user/allcities?email 以返回城市。如需成功响应,请参阅以下屏幕,
结论
我试图涵盖 JWT 的基础知识、生成令牌、使用此令牌调用保护 API 端点、注册用户、数据库迁移、实体框架核心 - 代码优先。在接下来的文章中,将重点关注刷新令牌和向用户添加角色。
REST API 安全最佳实践
以下是 REST API 的一些通用安全设计指南和最佳实践:
始终以最低权限启动 REST API 访问。默认的 API 访问应该是拒绝访问。如果用户已通过身份验证,则默认访问权限应为对尽可能少的数据进行只读访问。可以为更高的角色添加更多的数据访问权限。
使用 HTTPS 确保 REST API 始终安全,并遵循最新的更新建议,例如 HTTPS 2.0。
不要在用户 ID、密码或电子邮件等登录字段中传递纯文本。始终传递安全的加密请求。
确保密码不是纯文本。使用哈希保存密码并使用重置密码选项。
切勿在 URL 中公开变量和其他数据。
如果可能的话,用时间戳、客户端信息和用户记录每个请求。
在开始将输入参数传递给后端之前,请始终对其进行验证。
使用标准和现代授权最佳实践,例如 OAuth。
让您自己和您的 API 了解最新的更新和建议。
确保 Web 服务器具有最新的补丁和安装。
感谢您的阅读,请在评论部分告诉我您的问题、想法或反馈。我感谢您的反馈和鼓励。
推荐一个有趣的网站,这里有丰富的代码教程及开发项目供参考学习,点击查看。



