• .NET Core 如何使用JWT进行身份认证和授权
  • 发布于 1周前
  • 35 热度
    0 评论
一、简介
我们做微服务开发,或者说做分布式开发,有一项技术我们是避不开的,那就是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 源码如下,红色是重要代码:            
// 堆代码 duidaima.com
using PatrickLiu.Net6API.Extensions;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.Configure<JWTTokenOption>(builder.Configuration.GetSection("JWTTokenOption"));
builder.Services.AddTransient<IJWTService, JWTService>();
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.MapControllers();
app.Run();
(2)、AuthenticationController 源码如下:
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using PatrickLiu.Net6API.Extensions;

namespace PatrickLiu.Net6API.AuthenticationCenter.Controllers
{
    [ApiController]
    [Route("api/[controller]/[action]")]
    public class AuthenticationController : ControllerBase
    {
        private readonly IJWTService _jWTService;

        /// <summary>
        /// 实例化。
        /// </summary>
        /// <param name="jWTService">注入服务。</param>
        public AuthenticationController(IJWTService jWTService)
        {
            _jWTService = jWTService;
        }

        /// <summary>
        /// 堆代码 duidaima.com
        /// 根据用户名和密码获取 Token。
        /// </summary>
        /// <param name="userName">用户名。</param>
        /// <param name="password">密码。</param>
        /// <returns></returns>
        [HttpPost]
        public string Login(string userName, string password)
        {
            if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password))
            {
                if (string.Compare(userName, "PatrickLiu", true) == 0 && string.Compare(password, "liulei123456", true) == 0)
                {
                    string token = _jWTService.GetToken(userName, password);
                    return JsonConvert.SerializeObject(new
                    {
                        Result = true,
                        Token = token
                    });
                }
            }
            return string.Empty;
        }
    }
}
(3)、appsettings.json 配置文件源码如下,红色字体要注意:
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "JWTTokenOption": {
    "Audience": "PatrickLiu.com",
    "Issuer": "PatrickLiu.com",
    "SecurityKey": "12333456677655ffrrffff"
  }
}

2、还有一个公共项目,用于存放公共类型。项目类型:PatrickLiu.Net6API.Extensions,项目类型:Net 6.0类库。   

(1)、IJWTService 源码如下:
namespace PatrickLiu.Net6API.Extensions
{
    /// <summary>
    /// 提供 Token 的服务。
    /// </summary>
    public interface IJWTService
    {
        /// <summary>
        /// 获取 Token。
        /// </summary>
        /// <param name="userName">用户名</param>
        /// <param name="password">密码</param>
        /// <returns></returns>
        string GetToken(string userName,string password);
    }
}
(2)、JWTService 源码如下:      
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;

namespace PatrickLiu.Net6API.Extensions
{
    /// <summary>
    /// 提供 Token 服务的实现。
    /// </summary>
    public sealed class JWTService : IJWTService
    {
        private readonly IOptionsMonitor<JWTTokenOption> _option;

        /// <summary>
        /// 实例化。
        /// </summary>
        /// <param name="option">注入选项。</param>
        public JWTService(IOptionsMonitor<JWTTokenOption> option)
        {
            _option = option;
        }

        /// <summary>
        ///  获取 Token。
        /// </summary>
        /// <param name="userName">用户名。</param>
        /// <param name="password">密码</param>
        /// <returns></returns>
        public string GetToken(string userName, string password)
        {
            #region 有效载荷

            var claims = new[] {
            new Claim(ClaimTypes.Name, userName),
            new Claim("NickName",userName),
            new Claim(ClaimTypes.Role,"Administrator"),
            new Claim("Password",password),
            };

            #endregion

            SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_option.CurrentValue.SecurityKey!));

            SigningCredentials signingCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

            JwtSecurityToken token = new JwtSecurityToken(
                issuer: _option.CurrentValue.Issuer!,
                audience: _option.CurrentValue.Audience!,
                claims: claims,
                expires: DateTime.Now.AddMinutes(5),
                signingCredentials: signingCredentials
                );

            string returnToken = new JwtSecurityTokenHandler().WriteToken(token);

            return returnToken;
        }
    }
}
(3)、JWTTokenOption 源码如下:
namespace PatrickLiu.Net6API.Extensions
{
    /// <summary>
    /// 用于接受配置数据实体类型。
    /// </summary>
    public sealed class JWTTokenOption
    {
        /// <summary>
        /// 获取或者设置接受者。
        /// </summary>
        public string? Audience { get; set; }

        /// <summary>
        /// 获取或者设置加密 key。
        /// </summary>
        public string? SecurityKey { get; set; }

        /// <summary>
        /// 获取或者设置发布者
        /// </summary>
        public string? Issuer { get; set; }
    }
}
(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】特性,红色部分要注意。
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace PatrickLiu.Net6API.Resouces.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class SecondController : ControllerBase
    {
        /// <summary>
        /// 这里使用 JWT 进行授权检查。
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [Authorize(AuthenticationSchemes =JwtBearerDefaults.AuthenticationScheme)]
        public object GetData()
        {
            return new
            {
                Id = 1234,
                Name = "PatrickLiu"
            };
        }
    }
}
(5)、appsettings.json 配置文件,这里的配置要和【PatrickLiu.Net6API.AuthenticationCenter】项目配置一样,红色部分要注意。
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "JWTTokenOption": {
    "Audience": "PatrickLiu.com",
    "Issuer": "PatrickLiu.com",
    "SecurityKey": "12333456677655ffrrffff"
  }
}
4、现在是Minimal WebAPI类型项目:
其实,WebAPI和MinimalAPI鉴权和授权总体都是差不多,差别不大。
(1)、我们先看一下项目截图,有一个直观感受,截图如下:
   
(2)、如果想做WebAPI的JWT的鉴权和授权,必须通过 Nuget 加载的程序包,名称如下:
Microsoft.AspNetCore.Authentication.JwtBearer
Microsoft.IdentityModel.Tokens
(3)、Program.cs 源码如下,红色标注要特别重要。
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using PatrickLiu.Net6API.Extensions;
using PatrickLiu.Net6API.MinimalAPI.Extension;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

#region 1、配置鉴权逻辑
// 堆代码 duidaima.com
//增加的鉴权逻辑,角色认证、策略认证都是支持的,和Net Core MVC 支持的一样。
JWTTokenOption tokenOption = new JWTTokenOption();
builder.Configuration.Bind("JWTTokenOption", tokenOption);
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(option =>
    {
        option.TokenValidationParameters = new TokenValidationParameters()
        {
            ValidateIssuer = true,//是否验证 Issuer(发行商)
            ValidateAudience = true,//是否验证 Audience(受众者)
            ValidateLifetime = true,//是否验证失效时间
            ValidateIssuerSigningKey = true,//是否验证 Issuer 的签名键
            ValidAudience = tokenOption.Audience,
            ValidIssuer = tokenOption.Issuer,// ValidAudience,ValidIssuer这两项的值要和验证中心的只保持一致。
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenOption.SecurityKey!))
        };
    });

#endregion

#region 2、配置授权逻辑

//在这里不支持增加授权信息,比如:builder.Services.AddAuthorization(JwtBearerDefaults.AuthenticationScheme);,这样写是不行的,  我们可以扩展实现 IAuthorizeData 来达到同样的目的。
builder.Services.AddAuthorization();

#endregion

var app = builder.Build();

app.UseSwagger();
app.UseSwaggerUI();

#region 3、使用鉴权授权中间件

app.UseAuthentication();
app.UseAuthorization();

#endregion

#region 4、实现授权要求

app.MapGet("/User", (int id, string name) =>
{
    return "Hello World!";
}).RequireAuthorization(new CustomAuthorizeData()
{
    AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme
    //可以增加角色授权
    //,Roles="",
    //可以增加策略授权。
    //Policy=""
});

app.MapGet("/Products", (HttpContext context) =>
{
    return new
    {
        Id = 123456,
        Name = "IPhone14 Pro Max",
        Price = 4839.23,
        DateOfProduction = "2023/2/12 23:22"
    };
});

#endregion

app.Run();
(4)、扩展 IAuthorizeData 接口实现CustomAuthorizeData,可以实现对MinimalAPI授权。
using Microsoft.AspNetCore.Authorization;

namespace PatrickLiu.Net6API.MinimalAPI.Extension
{
    public sealed class CustomAuthorizeData : IAuthorizeData
    {
        public string? Policy { get; set; }
        public string? Roles { get; set; }
        public string? AuthenticationSchemes { get; set; }
    }
}
(5)、appsettings.json 配置文件源码如下,红色部分是重要代码。
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "JWTTokenOption": {
    "Audience": "PatrickLiu.com",
    "Issuer": "PatrickLiu.com",
    "SecurityKey": "12333456677655ffrrffff"
  }
}
三、结束语
好了,今天就写到这里了,现在总结一下,如果想要给WebAPI或者MinimalAPI实现授权和鉴权,总体步骤是差不多的,第一步,都要启用连个中间件,就是授权和鉴权中间件(app.UseAuthorization()、app.UseAuthentication()),然后要配置鉴权的配置逻辑,差别就在授权方面,普通WebAPI直接通过 AuthorizeAttribute 特性标注在需要授权的方法或者 Controller 类型上就可以。普通WebAPI支持所有的角色授权、策略授权等方法,并且也支持所有的Filter。MinimalAPI要实现授权,要在GetXXX方法后面调用RequireAuthorization()来实现授权的内容。不忘初心,继续努力,老天不会辜负努力的人。
用户评论