如何在 Net6.0 中對 WebAPI 進行 JWT 認證和授權

2023-03-10 12:01:42

一、簡介
    我們做微服務開發,或者說做分散式開發,有一項技術我們是避不開的,那就是WebAPI,在 Net6.0中,有兩類 WebAPI,一類是極簡 WebAPI,它砍掉了很多冗餘的東西,更純粹的是做 API的,也更適合做微服務的開發。另外一類就是我們通常使用的正常 API,這個沒得說,也是我們使用最多的。
    我們開發的API必須做鑑權和授權操作,否則,就成了裸跑了,那對於系統來說是很危險的。總體來說,一般的WebAPI和MinimalAPI鑑權的過程都是差不多的,但是也有微小的差異。今天我們就來詳細說一下,內容很簡單,大家有了基礎可以隨意去擴充套件。

      開發環境詳情:
          作業系統:Windows 10 Professional
          開發工具:Visual Studio 2022
          開發語言:C#
          開發平臺:Asp.Net Core 6.0 WebAPI
          測試工具:PostMan

二、具體步驟
    我們在做具體的開發前,也要做一些準備工作,我們要做鑑權和授權,尤其是要使用 JWT 做鑑權和授權,這裡麵包含兩個專案,一個是鑑權伺服器,用於提供 JWTToken,另外一個專案是需要做鑑權和授權的具體 WebAPI 專案。有了準備,我們就可以開始了。

    1、第一個專案:PatrickLiu.Net6API.AuthenticationCenter,用於提供獲取 Token 介面。
        

        (1)、Program.cs 原始碼如下,紅色是重要程式碼:            

 1 using PatrickLiu.Net6API.Extensions;
 2 
 3 var builder = WebApplication.CreateBuilder(args);
 4 
 5 builder.Services.AddControllers();
 6 builder.Services.AddEndpointsApiExplorer();
 7 builder.Services.AddSwaggerGen();
 8 
 9 builder.Services.Configure<JWTTokenOption>(builder.Configuration.GetSection("JWTTokenOption"));
10 builder.Services.AddTransient<IJWTService, JWTService>();
11 
12 var app = builder.Build();
13 
14 app.UseSwagger();
15 app.UseSwaggerUI();
16 
17 app.MapControllers();
18 
19 app.Run();

          (2)、AuthenticationController 原始碼如下:

 1 using Microsoft.AspNetCore.Mvc;
 2 using Newtonsoft.Json;
 3 using PatrickLiu.Net6API.Extensions;
 4 
 5 namespace PatrickLiu.Net6API.AuthenticationCenter.Controllers
 6 {
 7     [ApiController]
 8     [Route("api/[controller]/[action]")]
 9     public class AuthenticationController : ControllerBase
10     {
11         private readonly IJWTService _jWTService;
12 
13         /// <summary>
14         /// 範例化。
15         /// </summary>
16         /// <param name="jWTService">注入服務。</param>
17         public AuthenticationController(IJWTService jWTService)
18         {
19             _jWTService = jWTService;
20         }
21 
22         /// <summary>
23         /// 根據使用者名稱和密碼獲取 Token。
24         /// </summary>
25         /// <param name="userName">使用者名稱。</param>
26         /// <param name="password">密碼。</param>
27         /// <returns></returns>
28         [HttpPost]
29         public string Login(string userName, string password)
30         {
31             if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password))
32             {
33                 if (string.Compare(userName, "PatrickLiu", true) == 0 && string.Compare(password, "liulei123456", true) == 0)
34                 {
35                     string token = _jWTService.GetToken(userName, password);
36                     return JsonConvert.SerializeObject(new
37                     {
38                         Result = true,
39                         Token = token
40                     });
41                 }
42             }
43             return string.Empty;
44         }
45     }
46 }

          (3)、appsettings.json 組態檔原始碼如下,紅色字型要注意:

 1 {
 2   "Logging": {
 3     "LogLevel": {
 4       "Default": "Information",
 5       "Microsoft.AspNetCore": "Warning"
 6     }
 7   },
 8   "AllowedHosts": "*",
 9   "JWTTokenOption": {
10     "Audience": "PatrickLiu.com",
11     "Issuer": "PatrickLiu.com",
12     "SecurityKey": "12333456677655ffrrffff"
13   }
14 }


    2、還有一個公共專案,用於存放公共型別。專案型別:PatrickLiu.Net6API.Extensions,專案型別:Net 6.0類庫。
        

        (1)、IJWTService 原始碼如下:

 1 namespace PatrickLiu.Net6API.Extensions
 2 {
 3     /// <summary>
 4     /// 提供 Token 的服務。
 5     /// </summary>
 6     public interface IJWTService
 7     {
 8         /// <summary>
 9         /// 獲取 Token。
10         /// </summary>
11         /// <param name="userName">使用者名稱</param>
12         /// <param name="password">密碼</param>
13         /// <returns></returns>
14         string GetToken(string userName,string password);
15     }
16 }

        (2)、JWTService 原始碼如下:      

 1 using Microsoft.Extensions.Options;
 2 using Microsoft.IdentityModel.Tokens;
 3 using System.IdentityModel.Tokens.Jwt;
 4 using System.Security.Claims;
 5 using System.Security.Cryptography;
 6 using System.Text;
 7 
 8 namespace PatrickLiu.Net6API.Extensions
 9 {
10     /// <summary>
11     /// 提供 Token 服務的實現。
12     /// </summary>
13     public sealed class JWTService : IJWTService
14     {
15         private readonly IOptionsMonitor<JWTTokenOption> _option;
16 
17         /// <summary>
18         /// 範例化。
19         /// </summary>
20         /// <param name="option">注入選項。</param>
21         public JWTService(IOptionsMonitor<JWTTokenOption> option)
22         {
23             _option = option;
24         }
25 
26         /// <summary>
27         ///  獲取 Token。
28         /// </summary>
29         /// <param name="userName">使用者名稱。</param>
30         /// <param name="password">密碼</param>
31         /// <returns></returns>
32         public string GetToken(string userName, string password)
33         {
34             #region 有效載荷
35 
36             var claims = new[] {
37             new Claim(ClaimTypes.Name, userName),
38             new Claim("NickName",userName),
39             new Claim(ClaimTypes.Role,"Administrator"),
40             new Claim("Password",password),
41             };
42 
43             #endregion
44 
45             SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_option.CurrentValue.SecurityKey!));
46 
47             SigningCredentials signingCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
48 
49             JwtSecurityToken token = new JwtSecurityToken(
50                 issuer: _option.CurrentValue.Issuer!,
51                 audience: _option.CurrentValue.Audience!,
52                 claims: claims,
53                 expires: DateTime.Now.AddMinutes(5),
54                 signingCredentials: signingCredentials
55                 );
56 
57             string returnToken = new JwtSecurityTokenHandler().WriteToken(token);
58 
59             return returnToken;
60         }
61     }
62 }

        (3)、JWTTokenOption 原始碼如下:

 1 namespace PatrickLiu.Net6API.Extensions
 2 {
 3     /// <summary>
 4     /// 用於接受設定資料實體型別。 
 5     /// </summary>
 6     public sealed class JWTTokenOption
 7     {
 8         /// <summary>
 9         /// 獲取或者設定接受者。
10         /// </summary>
11         public string? Audience { get; set; }
12 
13         /// <summary>
14         /// 獲取或者設定加密 key。
15         /// </summary>
16         public string? SecurityKey { get; set; }
17 
18         /// <summary>
19         /// 獲取或者設定釋出者
20         /// </summary>
21         public string? Issuer { get; set; }
22     }
23 }

        (4)、授權伺服器執行起來如下:

          

          


    3、現在是普通WebAPI型別專案:

        (1)、我們先看一下專案截圖,有一個直觀感受,截圖如下:
            


        (2)、如果想做WebAPI的JWT的鑑權和授權,必須通過 Nuget 載入的程式包,名稱如下:
            Microsoft.AspNetCore.Authentication.JwtBearer
            Microsoft.IdentityModel.Tokens
        (3)、Program.cs原始碼如下,紅色部分表示健全和授權的重要部分。

 1 using Microsoft.AspNetCore.Authentication.JwtBearer;
 2 using Microsoft.IdentityModel.Tokens;
 3 using PatrickLiu.Net6API.Extensions;
 4 using System.Text;
 5 
 6 var builder = WebApplication.CreateBuilder(args);
 7 
 8 builder.Services.AddControllers();
 9 builder.Services.AddEndpointsApiExplorer();
10 builder.Services.AddSwaggerGen();
11 
12 #region 設定鑑權
13 
14 //增加的鑑權邏輯,角色認證、策略認證都是支援的,和Net Core MVC 支援的一樣。
15 JWTTokenOption tokenOption = new JWTTokenOption();
16 builder.Configuration.Bind("JWTTokenOption", tokenOption);
17 //builder.Services.Configure<JWTTokenOption>(builder.Configuration.GetSection("JWTTokenOption"));
18 builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
19     .AddJwtBearer(option =>
20 {
21     option.TokenValidationParameters = new TokenValidationParameters()
22     {
23         ValidateIssuer = true,//是否驗證 Issuer(發行商)
24         ValidateAudience = true,//是否驗證 Audience(受眾者)
25         ValidateLifetime = true,//是否驗證失效時間
26         ValidateIssuerSigningKey = true,//是否驗證 Issuer 的簽名鍵
27         ValidAudience=tokenOption.Audience,
28         ValidIssuer=tokenOption.Issuer,// ValidAudience,ValidIssuer這兩項的值要和驗證中心的只保持一致。
29         IssuerSigningKey=new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenOption.SecurityKey!))         
30     };
31 });
32 
33 #endregion
34 
35 var app = builder.Build();
36 
37 app.UseSwagger();
38 app.UseSwaggerUI();
39 
40 app.UseAuthentication();
41 app.UseAuthorization();42 
43 app.MapControllers();
44 
45 app.Run();


        (4)、要實現授權的Controller增加【Authorize】特性,紅色部分要注意。

 1 using Microsoft.AspNetCore.Authentication.JwtBearer;
 2 using Microsoft.AspNetCore.Authorization;
 3 using Microsoft.AspNetCore.Mvc;
 4 
 5 namespace PatrickLiu.Net6API.Resouces.Controllers
 6 {
 7     [Route("api/[controller]")]
 8     [ApiController]
 9     public class SecondController : ControllerBase
10     {
11         /// <summary>
12         /// 這裡使用 JWT 進行授權檢查。
13         /// </summary>
14         /// <returns></returns>
15         [HttpGet]
16         [Authorize(AuthenticationSchemes =JwtBearerDefaults.AuthenticationScheme)]
17         public object GetData()
18         {
19             return new
20             {
21                 Id = 1234,
22                 Name = "PatrickLiu"
23             };
24         }
25     }
26 }


        (5)、appsettings.json 組態檔,這裡的設定要和【PatrickLiu.Net6API.AuthenticationCenter】專案設定一樣,紅色部分要注意。

 1 {
 2   "Logging": {
 3     "LogLevel": {
 4       "Default": "Information",
 5       "Microsoft.AspNetCore": "Warning"
 6     }
 7   },
 8   "AllowedHosts": "*",
 9   "JWTTokenOption": {
10     "Audience": "PatrickLiu.com",
11     "Issuer": "PatrickLiu.com",
12     "SecurityKey": "12333456677655ffrrffff"
13   }
14 }


    4、現在是Minimal WebAPI型別專案:
        其實,WebAPI和MinimalAPI鑑權和授權總體都是差不多,差別不大。
        (1)、我們先看一下專案截圖,有一個直觀感受,截圖如下:
            
        (2)、如果想做WebAPI的JWT的鑑權和授權,必須通過 Nuget 載入的程式包,名稱如下:
            Microsoft.AspNetCore.Authentication.JwtBearer
            Microsoft.IdentityModel.Tokens

        (3)、Program.cs 原始碼如下,紅色標註要特別重要。

 1 using Microsoft.AspNetCore.Authentication.JwtBearer;
 2 using Microsoft.IdentityModel.Tokens;
 3 using PatrickLiu.Net6API.Extensions;
 4 using PatrickLiu.Net6API.MinimalAPI.Extension;
 5 using System.Text;
 6 
 7 var builder = WebApplication.CreateBuilder(args);
 8 
 9 builder.Services.AddEndpointsApiExplorer();
10 builder.Services.AddSwaggerGen();
11 
12 #region 1、設定鑑權邏輯
13 
14 //增加的鑑權邏輯,角色認證、策略認證都是支援的,和Net Core MVC 支援的一樣。
15 JWTTokenOption tokenOption = new JWTTokenOption();
16 builder.Configuration.Bind("JWTTokenOption", tokenOption);
17 builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
18     .AddJwtBearer(option =>
19     {
20         option.TokenValidationParameters = new TokenValidationParameters()
21         {
22             ValidateIssuer = true,//是否驗證 Issuer(發行商)
23             ValidateAudience = true,//是否驗證 Audience(受眾者)
24             ValidateLifetime = true,//是否驗證失效時間
25             ValidateIssuerSigningKey = true,//是否驗證 Issuer 的簽名鍵
26             ValidAudience = tokenOption.Audience,
27             ValidIssuer = tokenOption.Issuer,// ValidAudience,ValidIssuer這兩項的值要和驗證中心的只保持一致。
28             IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenOption.SecurityKey!))
29         };
30     });
31 
32 #endregion
33 
34 #region 2、設定授權邏輯
35 
36 //在這裡不支援增加授權資訊,比如:builder.Services.AddAuthorization(JwtBearerDefaults.AuthenticationScheme);,這樣寫是不行的,
  我們可以擴充套件實現 IAuthorizeData 來達到同樣的目的。
37 builder.Services.AddAuthorization();
38 
39 #endregion
40 
41 var app = builder.Build();
42 
43 app.UseSwagger();
44 app.UseSwaggerUI();
45 
46 #region 3、使用鑑權授權中介軟體
47 
48 app.UseAuthentication();
49 app.UseAuthorization();
50 
51 #endregion
52 
53 #region 4、實現授權要求
54 
55 app.MapGet("/User", (int id, string name) =>
56 {
57     return "Hello World!";
58 }).RequireAuthorization(new CustomAuthorizeData()
59 {
60     AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme
61     //可以增加角色授權
62     //,Roles="",
63     //可以增加策略授權。
64     //Policy=""
65 });
66 
67 app.MapGet("/Products", (HttpContext context) =>
68 {
69     return new
70     {
71         Id = 123456,
72         Name = "IPhone14 Pro Max",
73         Price = 4839.23,
74         DateOfProduction = "2023/2/12 23:22"
75     };
76 });
77 
78 #endregion
79 
80 app.Run();


        (4)、擴充套件 IAuthorizeData 介面實現CustomAuthorizeData,可以實現對MinimalAPI授權。

 1 using Microsoft.AspNetCore.Authorization;
 2 
 3 namespace PatrickLiu.Net6API.MinimalAPI.Extension
 4 {
 5     public sealed class CustomAuthorizeData : IAuthorizeData
 6     {
 7         public string? Policy { get; set; }
 8         public string? Roles { get; set; }
 9         public string? AuthenticationSchemes { get; set; }
10     }
11 }


        (5)、appsettings.json 組態檔原始碼如下,紅色部分是重要程式碼。

 1 {
 2   "Logging": {
 3     "LogLevel": {
 4       "Default": "Information",
 5       "Microsoft.AspNetCore": "Warning"
 6     }
 7   },
 8   "AllowedHosts": "*",
 9   "JWTTokenOption": {
10     "Audience": "PatrickLiu.com",
11     "Issuer": "PatrickLiu.com",
12     "SecurityKey": "12333456677655ffrrffff"
13   }
14 }


三、結束語

       好了,今天就寫到這裡了,現在總結一下,如果想要給WebAPI或者MinimalAPI實現授權和鑑權,總體步驟是差不多的,第一步,都要啟用連個中介軟體,就是授權和鑑權中介軟體(app.UseAuthorization()、app.UseAuthentication()),然後要設定鑑權的設定邏輯,差別就在授權方面,普通WebAPI直接通過 AuthorizeAttribute 特性標註在需要授權的方法或者 Controller 型別上就可以。普通WebAPI支援所有的角色授權、策略授權等方法,並且也支援所有的Filter。MinimalAPI要實現授權,要在GetXXX方法後面呼叫RequireAuthorization()來實現授權的內容。不忘初心,繼續努力,老天不會辜負努力的人。