JWT在ASP.NET Core3.1的应用
本文是一篇偏实战的博客,完整项目下载地址:https://gitee.com/hanyixuan_net/donet-core
我们将演示如何在ASP.NET CORE3.1 API中使用JWT(JSON Web Token)身份认证。
本次示例有两个路由,以演示如何使用JWT进行身份验证和使用JWT访问受限路由:
/user/authenticate -接受HTTP POST请求(正文中包含用户名和密码)的公共路由。如果用户名和密码正确,则返回JWT身份验证Token和用户详细信息。
/user -如果HTTP授权标头包含有效的JWT Token,则该安全路由接受HTTP GET 请求并返回应用程序中的所有用户的列表;如果没有令牌或者令牌无效,则返回401未经授权的响应。
开发ASP.NET CORE 3.1应用程序所需的工具
本地开发ASP.NET CORE应用程序需要的下载和安装的工具如下:
.NET Core SDK 报告.NET Core运行时和命令行工具
Visual Studio Code 支持windows mac linux的代码编辑器
在本地运行示例 ASP.NET Core JWT身份验证API
- 从码云上下载和克隆代码 https://gitee.com/hanyixuan_net/donet-core
- 在项目的根目录(.csproj文件所在的目录)打开命令行运行 dotnet run来启动API。启动之后会看到代码会运行在localhost:1022的地址,可以通过使用Postman进行测试,也可以用单页面应用程序(Angular,React或者Vue)进行调用。
使用Postman测试ASP.NET Core API
Postman可以用来测试API,墙裂推荐,下载地址:https://www.getpostman.com/.
以下是关于如何使用Postman调用api获取JWT令牌来进行身份验证,然后使用JWT令牌发出经过身份认证的请求以从api检索用户列表的说明。
要使用API通过Postman验证用户并获取JWT令牌,有以下几个步骤:
- 选择最上面选项卡“+New”,选择“Request”
- 将http请求方法改为“Post”
- 在url文本框中输入要请求的本地Url Api:http://localhost:1022/user/authenticate
- 在地址栏下面选择"Body"选项卡,将正文类型单选按钮更改为“Raw”,并将格式下拉框选择器更改为“Json”。
- 在“Body”下面的文本框输入包含测试用户名和密码的Json对象
- 点击“Send”按钮,应该会收到一个“200 OK”的响应在响应文本中包含了带有JWT令牌的用户详细信息。请复制这个Token,下一步会用它来验证。
这是一张Postman请求之后验证成功并获取到Token之后的图:
获取到Token之后,接下来我们将用获取到的Token做认证请求,有以下几步:
- 选择最上面选项卡“+New”,选择“Request”
- 将http请求方法改成“GET”
- 在url文本框中输入要请求的本地Url api: http://localhost:1022/user/
- 在URL框下面选择“Authorization”,然后将类型Type改为“Bearer Token”,将上一步得到的Token粘贴到Token字段值中。
- 点击“Send”按钮,应该会收到“200 OK”的响应,其中包含一个Json数组,Json数组中包含系统中的所有用户信息(示例中仅一个测试用户)
下面是Postman中通过Token获取所有用户的截图:
ASP.NET Core JWT API项目结构
下面主要对几个重要的代码文件做一些简要说明
/Controllers/UserController.cs
定义和处理和用户相关的api的所有api的路由,包括身份验证和标准的CRUD操作。在每个路由中,控制器都调用用户服务(UserService)以执行所需要的操作,这使得业务逻辑和数据访问代码完全隔离。
UserController使用了[Authorize]属性来保护控制器,但是Authenticate例外,Authenticate使用了[AllowAnonymous]属性覆盖了控制器上的[Authorize]属性来允许公共访问。
1 using Microsoft.AspNetCore.Mvc;
2 using Microsoft.AspNetCore.Authorization;
3 using JwtAuthDemo.IServices;
4 using System.Linq;
5 using JwtAuthDemo.Models;
6 namespace JwtAuthDemo.Controllers
7 {
8 [Authorize]
9 [ApiController]
10 [Route("[controller]")]
11 public class UserController:ControllerBase
12 {
13 private IUserService _userService;
14 public UserController(IUserService userService)
15 {
16 _userService=userService;
17 }
18 [AllowAnonymous]
19 [HttpPost("authenticate")]
20 public IActionResult Authenticate([FromBody]AuthenticateModel model)
21 {
22 var user=_userService.Authenticate(model.UserName,model.Password);
23 if(User==null)
24 {
25 return BadRequest(new {message="用户名或者密码不正确"});
26 }
27 return Ok(user);
28 }
29 public IActionResult GetAll()
30 {
31 var users=_userService.GetAll();
32 return Ok(users);
33 }
34
35 }
36 }
/Entities/User.cs
实体类用于在应用程序不同部分之间(如服务和控制器)传递数据,也可以用于从控制器操作方法返回HTTP响应数据。但是有时候控制器方法需要返回多种数据类型实体或者其他一些自定义数据,这个是很好就需要在Models文件夹中创建自定义的模型类数据进行响应。
1 namespace JwtAuthDemo.Entities
2 {
3 public class User
4 {
5 public int Id { get; set; }
6 public string FirstName{get;set;}
7 public string LastName{get;set;}
8 public string UserName{get;set;}
9 public string Password{get;set;}
10 public string Token{get;set;}
11 }
12 }
/Helper/AppSettings.cs
AppSettings.cs中定义了appsettings.json中的属性,用于通过依赖注入到类中的对象来访问应用程序设置。如用户服务通过注入构造函数中的IOptions<AppSettings> appSettings 对象访问应用程序设置。
在Startup.cs的ConfigureServices
中完成配置文件sections到类的映射。
1 namespace JwtAuthDemo.Helper
2 {
3 public class AppSettings
4 {
5 public string Secret{get;set;}
6 }
7 }
/Helpers/ExtensionMethods.cs
ExtensionMethods类中一般添加一些有利于程序功能的其他方法。
在本例中,添加了两个方法,用于从User实例和IEnumerable<User>集合中去掉密码。这些方法会在UserService中的Authenticate和GetAll方法中调用,以确保返回的对象不包含密码。
1 using System.Collections.Generic;
2 using System.Linq;
3 using JwtAuthDemo.Entities;
4 namespace JwtAuthDemo.Helper
5 {
6 public static class ExtensionMethods
7 {
8 public static IEnumerable<User> WithoutPasswords(this IEnumerable<User> users){
9 return users.Select(x=>x.WithoutPassword());
10 }
11 public static User WithoutPassword(this User user){
12 user.Password=null;
13 return user;
14 }
15 }
16 }
/Models/AuthenticateModel.cs
AuthenticateModel为api的/users/authenticate 路由定义了传入请求的参数。档次收到对路由HTTP POST请求时,来自正文的数据将绑定到AuthenticateModel的实例,并经过验证传递给方法。
1 using System.ComponentModel.DataAnnotations;
2 namespace JwtAuthDemo.Models
3 {
4 public class AuthenticateModel
5 {
6 [Required]
7 public string UserName{get;set;}
8 [Required]
9 public string Password{get;set;}
10 }
11 }
/IServices/IUserService.cs
定义用户服务的接口
1 using System.Collections.Generic;
2 using JwtAuthDemo.Entities;
3 namespace JwtAuthDemo.IServices
4 {
5 public interface IUserService
6 {
7 User Authenticate(string name,string password);
8 IEnumerable<User> GetAll();
9 }
10 }
/Services/UserService.cs
UserService类中包含一个用于验证用户凭据并返回JWT令牌的方法,以及一个用于获取程序中所有用户的方法。
本项目中对用户数组做了硬编码,以使本文更专注与JWT身份验证。在生产环境,建议使用哈希密码将用户存储在数据库中。
成功认证后,Authenticate方法使用JwtSecurityTokenHandler类生成JWT(JSON Web令牌),该类生成使用存储在appsettings.json中的密钥进行数字签名的令牌。 JWT令牌返回到客户端应用程序,该客户端应用程序必须将其包含在后续请求的HTTP授权标头中以保护路由。
1 using System;
2 using System.Collections.Generic;
3 using System.IdentityModel.Tokens.Jwt;
4 using System.Security.Claims;
5 using Microsoft.Extensions.Options;
6 using Microsoft.IdentityModel.Tokens;
7 using System.Linq;
8 using System.Text;
9 using JwtAuthDemo.Entities;
10 using JwtAuthDemo.IServices;
11 using JwtAuthDemo.Helper;
12 namespace JwtAuthDemo.Services
13 {
14 public class UserService : IUserService
15 {
16 //这里为了简化流程将用户信息写死,真正在用的时候肯定是从数据库或者其他数据源读取
17 private List<User> _users=new List<User>
18 {
19 new User{
20 Id=1,
21 FirstName="yixuan",
22 LastName="han",
23 UserName="hanyixuan",
24 Password="hanyixuan"
25
26 }
27 };
28 private readonly AppSettings _appSettings;
29 public UserService(IOptions<AppSettings> appSettings)
30 {
31 _appSettings=appSettings.Value;
32 }
33 public User Authenticate(string name, string password)
34 {
35 var user=_users.SingleOrDefault(x=>x.UserName==name && x.Password==password);
36 //如果未找到user,则返回null
37 if(user==null)
38 return null;
39
40 //否则验证成功,生成jwt token
41 var tokenHandler=new JwtSecurityTokenHandler();
42 var key=Encoding.ASCII.GetBytes(_appSettings.Secret);
43 var tokenDescriptor=new SecurityTokenDescriptor
44 {
45 Subject=new ClaimsIdentity(new Claim[]{
46 new Claim(ClaimTypes.Name,user.Id.ToString())
47 }),
48 Expires=DateTime.UtcNow.AddDays(7),
49 SigningCredentials=new SigningCredentials(new SymmetricSecurityKey(key),SecurityAlgorithms.HmacSha256Signature)
50 };
51 var token=tokenHandler.CreateToken(tokenDescriptor);
52 user.Token=tokenHandler.WriteToken(token);
53 return user.WithoutPassword();
54
55 }
56
57 public IEnumerable<User> GetAll()
58 {
59 return _users.WithoutPasswords();
60 }
61 }
62 }
/appsettings.Development.json
具有特定于开发环境的应用程序设置的配置文件
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}
/appsettings.json
包含所有环境的应用程序设置的根配置文件。
重要说明:Api使用Secret属性来签名和验证用于身份验证的JWT令牌,并使用您自己的随机字符串对其进行更新,以确保其他人无法生成JWT来获得对您的应用程序的未授权访问
{
"AppSettings": {
"Secret": "THIS IS USED TO SIGN AND VERIFY JWT TOKENS, REPLACE IT WITH YOUR OWN SECRET, IT CAN BE ANY STRING"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
/Program.cs
Program.cs是一个控制台应用程序,它是启动该应用程序的主要入口点。它使用IHostBuilder的实例配置和启动web api主机和web服务器。ASP.NET Core应用程序需要一个host去执行。
Kestrel是示例中使用的Web服务器,它是ASP.NET Core的一种新的狂平台的web服务器,默认包含在新项目模板中。Kestrel本身可以很好的用于内部应用程序的开发,但是在一些公共网站和应用程序中,建议使用反向代理服务器(IIS,Apache,Nginx),一个反向代理服务器接收来自网络的HTTP请求并且在经过一些初步处理后将请求转发到Kestrel。
1 using Microsoft.AspNetCore.Hosting;
2 using Microsoft.Extensions.Hosting;
3
4 namespace JwtAuthDemo
5 {
6 public class Program
7 {
8 public static void Main(string[] args)
9 {
10 CreateHostBuilder(args).Build().Run();
11 }
12 public static IHostBuilder CreateHostBuilder(string[] args) =>
13 Host.CreateDefaultBuilder(args)
14 .ConfigureWebHostDefaults(webBuilder =>
15 {
16 webBuilder.UseStartup<Startup>()
17 .UseUrls("http://localhost:1022");
18 });
19 }
20 }
/Startup.cs
Startup类用于配置应用程序的请求管道以及如何处理这些请求。
1 using Microsoft.AspNetCore.Builder;
2 using Microsoft.AspNetCore.Hosting;
3 using Microsoft.Extensions.Configuration;
4 using Microsoft.Extensions.DependencyInjection;
5 using JwtAuthDemo.IServices;
6 using JwtAuthDemo.Services;
7 using JwtAuthDemo.Helper;
8 using Microsoft.AspNetCore.Authentication.JwtBearer;
9 using Microsoft.IdentityModel.Tokens;
10 using System.Text;
11 namespace JwtAuthDemo
12 {
13 public class Startup
14 {
15 public Startup(IConfiguration configuration)
16 {
17 Configuration = configuration;
18 }
19
20 public IConfiguration Configuration { get; }
21
22 // This method gets called by the runtime. Use this method to add services to the container.
23 public void ConfigureServices(IServiceCollection services)
24 {
25 services.AddCors();
26 services.AddControllers();
27 var appSettingsSection = Configuration.GetSection("AppSettings");
28 services.Configure<AppSettings>(appSettingsSection);
29 // configure jwt authentication
30 var appSettings = appSettingsSection.Get<AppSettings>();
31 var key = Encoding.ASCII.GetBytes(appSettings.Secret);
32 services.AddAuthentication(x =>
33 {
34 x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
35 x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
36 })
37 .AddJwtBearer(x =>
38 {
39 x.RequireHttpsMetadata = false;
40 x.SaveToken = true;
41 x.TokenValidationParameters = new TokenValidationParameters
42 {
43 ValidateIssuerSigningKey = true,
44 IssuerSigningKey = new SymmetricSecurityKey(key),
45 ValidateIssuer = false,
46 ValidateAudience = false
47 };
48 });
49 // configure DI for application services
50 services.AddScoped<IUserService, UserService>();
51 }
52
53 // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
54 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
55 {
56 app.UseRouting();
57
58 // global cors policy
59 app.UseCors(x => x
60 .AllowAnyOrigin()
61 .AllowAnyMethod()
62 .AllowAnyHeader());
63
64 app.UseAuthentication();
65 app.UseAuthorization();
66
67 app.UseEndpoints(endpoints => {
68 endpoints.MapControllers();
69 });
70 }
71 }
72 }
/JwtAuthDemo.csproj
csproj(C#项目)是一个基于MSBuild的文件,其中包含目标框架和应用程序的NuGet包依赖项信息。
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.6.0" />
</ItemGroup>
</Project>
下一篇,我们将会介绍如何用单页面应用程序Vue.js来调用此API.