如何在 Net6.0 中对 WebAPI 进行 JWT 认证和授权

发布时间 2023-03-22 19:13:36作者: 可均可可

一、简介
    我们做微服务开发,或者说做分布式开发,有一项技术我们是避不开的,那就是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()来实现授权的内容。不忘初心,继续努力,老天不会辜负努力的人。