Asp.Net Core 5 REST API 使用 JWT 身份验证 - Step by Step
翻译自 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
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://github.com/mohamadlawand087/v7-RestApiNetCoreAuthentication ︎
Asp.Net Core 5 REST API 使用 JWT 身份验证 - Step by Step的更多相关文章
- ASP.NET Core 如何用 Cookie 来做身份验证
前言 本示例完全是基于 ASP.NET Core 3.0.本文核心是要理解 Claim, ClaimsIdentity, ClaimsPrincipal,读者如果有疑问,可以参考文章 理解ASP.NE ...
- Asp Net Core 5 REST API 使用 RefreshToken 刷新 JWT - Step by Step
翻译自 Mohamad Lawand 2021年1月25日的文章 <Refresh JWT with Refresh Tokens in Asp Net Core 5 Rest API Step ...
- Asp.Net Core 5 REST API - Step by Step
翻译自 Mohamad Lawand 2021年1月19日的文章 <Asp.Net Core 5 Rest API Step by Step> [1] 在本文中,我们将创建一个简单的 As ...
- ASP.NET Core Web API中带有刷新令牌的JWT身份验证流程
ASP.NET Core Web API中带有刷新令牌的JWT身份验证流程 翻译自:地址 在今年年初,我整理了有关将JWT身份验证与ASP.NET Core Web API和Angular一起使用的详 ...
- angular4和asp.net core 2 web api
angular4和asp.net core 2 web api 这是一篇学习笔记. angular 5 正式版都快出了, 不过主要是性能升级. 我认为angular 4还是很适合企业的, 就像.net ...
- ASP.NET Core 中基于 API Key 对私有 Web API 进行保护
这两天遇到一个应用场景,需要对内网调用的部分 web api 进行安全保护,只允许请求头账户包含指定 key 的客户端进行调用.在网上找到一篇英文博文 ASP.NET Core - Protect y ...
- ASP.NET Core WebApi构建API接口服务实战演练
一.ASP.NET Core WebApi课程介绍 人生苦短,我用.NET Core!提到Api接口,一般会想到以前用到的WebService和WCF服务,这三个技术都是用来创建服务接口,只不过Web ...
- 使用ASP.NET Core构建RESTful API的技术指南
译者荐语:利用周末的时间,本人拜读了长沙.NET技术社区翻译的技术标准<微软RESTFul API指南>,打算按照步骤写一个完整的教程,后来无意中看到了这篇文章,与我要写的主题有不少相似之 ...
- 温故知新,使用ASP.NET Core创建Web API,永远第一次
ASP.NET Core简介 ASP.NET Core是一个跨平台的高性能开源框架,用于生成启用云且连接Internet的新式应用. 使用ASP.NET Core,您可以: 生成Web应用和服务.物联 ...
随机推荐
- uniapp 在h5和小程序上使用高德获取用户城市位置
开发文档 https://lbs.amap.com/api 错误状态 https://lbs.amap.com/api/webservice/guide/tools/info/ 虽然用的高德但是你还需 ...
- 同样是NGK官方推出的代币,SPC与BGV有何异同?
近日,币圈又火热了起来,而这次火热是由NGK搅动的.原来,NGK官方空投了200万枚SPC,用于奖励NGK算力持有者.当前,已经有一部分算力持有者获得了SPC奖励,有的算力持有者获得的SPC数量惊人, ...
- NGK.IO会是一个投资优质项目吗?
互联网发展至今,技术已经高度成熟,人们发现了互联网的好处后,互联网逐渐渗入到家家户户.随着时代的变迁,人们对HTTP长期作为主流霸占互联网食物链的顶端感到不满足.当人类开始变得挑剔,HTTP的劣势就逐 ...
- 联童科技基于incubator-dolphinscheduler从0到1构建大数据调度平台之路
联童科技是一家智能化母婴童产业平台,从事母婴童行业以及互联网技术多年,拥有丰富的母婴门店运营和系统开发经验,在会员经营和商品经营方面,能够围绕会员需求,深入场景,更贴近合作伙伴和消费者,提供最优服务产 ...
- RabbitMQ之TTL(Time-To-Live 过期时间)
本文转载自RabbitMQ之TTL(Time-To-Live 过期时间) 概述 RabbitMQ可以对消息和队列设置TTL. 目前有两种方法可以设置.第一种方法是通过队列属性设置,队列中所有消息都有相 ...
- 使用Maven新建SpringBoot工程
最近用IDEA插件创建Springboot项目,总是403,估计被墙了! 那么这里在提供两种方法 1.从官网下载模板,导入IDEA内 2.使用Maven创建 方法一:打开 https://start. ...
- 《C++ Primer》笔记 第10章 泛型算法
迭代器令算法不依赖于容器,但算法依赖于元素类型的操作. 算法永远不会执行容器的操作.算法永远不会改变底层容器的大小. accumulate定义在头文件numeric中,接受三个参数,前两个指出需要求和 ...
- Spring的IOC常用注解(含源码)
一.容器中注入组件 1,包扫描 + 组件标注注解 源码:Demo01_ComponentScan a)组件标注 @Controller @Service @Repository @Component ...
- MyBatis(一):JDBC使用存在的问题
JDBC使用步骤: a:加载 JDBC 驱动程序 b:创建数据库的连接对象Connection c:根据链接获取Statement d:拼接SQL语句及设置参数 e:执行SQL并获取结果集 f:关闭使 ...
- hibernate中关系映射的配置问题
部门和员工属于一对多的关系 员工的账户属于一对一关系 账户和权限属于多对多关系 department.hbm.xml 1 <hibernate-mapping> 2 <class n ...