当前位置 博文首页 > 码农译站:使用JWT创建安全的ASP.NET Core Web API
在本文中,你将学习如何在ASP.NET Core Web API中使用JWT身份验证。我将在编写代码时逐步简化。我们将构建两个终结点,一个用于客户登录,另一个用于获取客户订单。这些api将连接到在本地机器上运行的SQL Server Express数据库。
JWT是什么?
JWT或JSON Web Token基本上是格式化令牌的一种方式,令牌表示一种经过编码的数据结构,该数据结构具有紧凑、url安全、安全且自包含特点。
JWT身份验证是api和客户端之间进行通信的一种标准方式,因此双方可以确保发送/接收的数据是可信的和可验证的。
JWT应该由服务器发出,并使用加密的安全密钥对其进行数字签名,以便确保任何攻击者都无法篡改在令牌内发送的有效payload及模拟合法用户。
JWT结构包括三个部分,用点隔开,每个部分都是一个base64 url编码的字符串,JSON格式:
Header.Payload.Signature:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxIiwicm9sZSI6IkFjY291bnQgTWFuYWdlciIsIm5iZiI6MTYwNDAxMDE4NSwiZXhwIjoxNjA0MDExMDg1LCJpYXQiOjE2MDQwMTAxODV9.XJLeLeUIlOZQjYyQ2JT3iZ-AsXtBoQ9eI1tEtOkpyj8
Header:表示用于对秘钥进行哈希的算法(例如HMACSHA256)
Payload:在客户端和API之间传输的数据或声明
Signature:Header和Payload连接的哈希
因为JWT标记是用base64编码的,所以可以使用jwt.io简单地研究它们或通过任何在线base64解码器。
由于这个特殊的原因,你不应该在JWT中保存关于用户的机密信息。
准备工作:
下载并安装Visual Studio 2019的最新版本(我使用的是Community Edition)。
下载并安装SQL Server Management Studio和SQL Server Express的最新更新。
开始我们的教程
让我们在Visual Studio 2019中创建一个新项目。项目命名为SecuringWebApiUsingJwtAuthentication。我们需要选择ASP.NET Core Web API模板,然后按下创建。Visual Studio现在将创建新的ASP.NET Core Web API模板项目。让我们删除WeatherForecastController.cs和WeatherForecast.cs文件,这样我们就可以开始创建我们自己的控制器和模型。
准备数据库
在你的机器上安装SQL Server Express和SQL Management Studio,
现在,从对象资源管理器中,右键单击数据库并选择new database,给数据库起一个类似CustomersDb的名称。
为了使这个过程更简单、更快,只需运行下面的脚本,它将创建表并将所需的数据插入到CustomersDb中。
USE [CustomersDb] GO /****** Object: Table [dbo].[Customer] Script Date: 11/9/2020 1:56:38 AM ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[Customer]( [Id] [int] IDENTITY(1,1) NOT NULL, [Username] [nvarchar](255) NOT NULL, [Password] [nvarchar](255) NOT NULL, [PasswordSalt] [nvarchar](50) NOT NULL, [FirstName] [nvarchar](255) NOT NULL, [LastName] [nvarchar](255) NOT NULL, [Email] [nvarchar](255) NOT NULL, [TS] [smalldatetime] NOT NULL, [Active] [bit] NOT NULL, [Blocked] [bit] NOT NULL, CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, _ ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO /****** Object: Table [dbo].[Order] Script Date: 11/9/2020 1:56:38 AM ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[Order]( [Id] [int] IDENTITY(1,1) NOT NULL, [Status] [nvarchar](50) NOT NULL, [Quantity] [int] NOT NULL, [Total] [decimal](19, 4) NOT NULL, [Currency] [char](3) NOT NULL, [TS] [smalldatetime] NOT NULL, [CustomerId] [int] NOT NULL, CONSTRAINT [PK_Order] PRIMARY KEY CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, _ ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO SET IDENTITY_INSERT [dbo].[Customer] ON GO INSERT [dbo].[Customer] ([Id], [Username], [Password], [PasswordSalt], _ [FirstName], [LastName], [Email], [TS], [Active], [Blocked]) _ VALUES (1, N'coding', N'ezVOZenPoBHuLjOmnRlaI3Q3i/WcGqHDjSB5dxWtJLQ=', _ N'MTIzNDU2Nzg5MTIzNDU2Nw==', N'Coding', N'Sonata', N'coding@codingsonata.com', _ CAST(N'2020-10-30T00:00:00' AS SmallDateTime), 1, 1) GO INSERT [dbo].[Customer] ([Id], [Username], [Password], [PasswordSalt], _ [FirstName], [LastName], [Email], [TS], [Active], [Blocked]) _ VALUES (2, N'test', N'cWYaOOxmtWLC5DoXd3RZMzg/XS7Xi89emB7jtanDyAU=', _ N'OTUxNzUzODUyNDU2OTg3NA==', N'Test', N'Testing', N'testing@codingsonata.com', _ CAST(N'2020-10-30T00:00:00' AS SmallDateTime), 1, 0) GO SET IDENTITY_INSERT [dbo].[Customer] OFF GO SET IDENTITY_INSERT [dbo].[Order] ON GO INSERT [dbo].[Order] ([Id], [Status], [Quantity], [Total], [Currency], [TS], _ [CustomerId]) VALUES (1, N'Processed', 5, CAST(120.0000 AS Decimal(19, 4)), _ N'USD', CAST(N'2020-10-25T00:00:00' AS SmallDateTime), 1) GO INSERT [dbo].[Order] ([Id], [Status], [Quantity], [Total], [Currency], [TS], _ [CustomerId]) VALUES (2, N'Completed', 2, CAST(750.0000 AS Decimal(19, 4)), _ N'USD', CAST(N'2020-10-25T00:00:00' AS SmallDateTime), 1) GO SET IDENTITY_INSERT [dbo].[Order] OFF GO ALTER TABLE [dbo].[Order] WITH CHECK ADD CONSTRAINT [FK_Order_Customer] _ FOREIGN KEY([CustomerId]) REFERENCES [dbo].[Customer] ([Id]) GO ALTER TABLE [dbo].[Order] CHECK CONSTRAINT [FK_Order_Customer] GO
创建实体文件夹,然后添加Customer.cs:
using System; using System.Collections.Generic; ? namespace SecuringWebApiUsingJwtAuthentication.Entities { public class Customer { public int Id { get; set; } public string Username { get; set; } public string Password { get; set; } public string PasswordSalt { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Email { get; set; } public DateTime TS { get; set; } public bool Active { get; set; } public bool Blocked { get; set; } public ICollection<Order> Orders { get; set; } } }
添加Order.cs:
using System; using System.Text.Json.Serialization; ? namespace SecuringWebApiUsingJwtAuthentication.Entities { public class Order { public int Id { get; set; } public string Status { get; set; } public int Quantity { get; set; } public decimal Total { get; set; } public string Currency { get; set; } public DateTime TS { get; set; } public int CustomerId { get; set; } [JsonIgnore] public Customer Customer { get; set; } } }
我将JsonIgnore属性添加到Customer对象,以便在对order对象进行Json序列化时隐藏它。
JsonIgnore属性来自 System.Text.Json.Serialization 命名空间,因此请确保将其包含在Order类的顶部。
现在我们将创建一个新类,它继承了EFCore的DbContext,用于映射数据库。
创建一个名为CustomersDbContext.cs的类:
using Microsoft.EntityFrameworkCore; ? namespace SecuringWebApiUsingJwtAuthentication.Entities { public class CustomersDbContext : DbContext { public DbSet<Customer> Customers { get; set; } public DbSet<Order> Orders { get; set; } public CustomersDbContext (DbContextOptions<CustomersDbContext> options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Customer>().ToTable("Customer"); modelBuilder.Entity<Order>().ToTable("Order"); } } }
Visual Studio现在将开始抛出错误,因为我们需要为EntityFramework Core和EntityFramework SQL Server引用NuGet包。
所以右键单击你的项目名称,选择管理NuGet包,然后下载以下包:
Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.SqlServer
一旦上述包在项目中被引用,就不会再看到VS的错误了。
现在转到Startup.cs文件,在ConfigureServices中将我们的dbcontext添加到服务容器:
services.AddDbContext<CustomersDbContext>(options=> options.UseSqlServer(Configuration.GetConnectionString("CustomersDbConnectionString")));
让我们打开appsettings.json文件,并在ConnectionStrings中创建连接字符串:
{ "ConnectionStrings": { "CustomersDbConnectionString": "Server=Home\\SQLEXPRESS;Database=CustomersDb; Trusted_Connection=True;MultipleActiveResultSets=true" }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*" }
现在我们已经完成了数据库映射和连接部分。
我们将继续准备服务中的业务逻辑。
创建服务
创建一个名称带有Requests的新文件夹。
我们在这里有一个LoginRequest.cs类,它表示客户将提供给登录的用户名和密码字段。
namespace SecuringWebApiUsingJwtAuthentication.Requests { public class LoginRequest { public string Username { get; set; } public string Password { get; set; } } }
为此,我们需要一个特殊的Response对象,返回有效的客户包括基本用户信息和他们的access token(JWT格式),这样他们就可以通过Authorization Header在后续请求授权api作为Bearer令牌。
因此,创建另一个文件夹,名称为Responses ,在其中,创建一个新的文件,名称为LoginResponse.cs:
namespace SecuringWebApiUsingJwtAuthentication.Responses { public class LoginResponse { public string Username { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Token { get; set; } } }
创建一个Interfaces文件夹:
添加一个新的接口ICustomerService.cs,这将包括客户登录的原型方法:
using SecuringWebApiUsingJwtAuthentication.Requests; using SecuringWebApiUsingJwtAuthentication.Responses; using System.Threading.Tasks; ? namespace SecuringWebApiUsingJwtAuthentication.Interfaces { public interface ICustomerService { Task<LoginResponse> Login(LoginRequest loginRequest); } }
现在是实现ICustomerService的部分。
创建一个新文件夹并将其命名为Services。
添加一个名为CustomerService.cs的新类:
using SecuringWebApiUsingJwtAuthentication.Entities; using SecuringWebApiUsingJwtAuthentication.Helpers;