本文是一篇偏實戰的博客,完整項目下載地址: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.