.NET 6实现基于JWT的Identity功能方法详解

2022-04-16 15:40:49
目录
需求目标原理与思路实现引入Identity组件添加认证服务使用JWT认证和定义授权方式引入认证授权中间件添加JWT配置增加认证用户Model实现认证服务CreateToken方法添加认证接口保护API资源验证验证1: 验证直接访问创建TodoList接口验证2: 获取Token验证3: 携带Token访问创建TodoList接口验证4: 更换Policy一点扩展总结参考资料

需求

在.NET Web API开发中还有一个很重要的需求是关于身份认证和授权的,这个主题非常大,所以本文不打算面面俱到地介绍整个主题,而仅使用.NET框架自带的认证和授权中间件去实现基于JWT的身份认证和授权功能。一些关于这个主题的基本概念也不会花很多的篇幅去讲解,我们还是专注在实现上。

目标

TodoList项目增加身份认证和授权功能。

原理与思路

为了实现身份认证和授权功能,我们需要使用.NET自带的AuthenticationAuthorization组件。在本文中我们不会涉及Identity Server的相关内容,这是另一个比较大的主题,因为许可证的变更,Identity Server 4将不再能够免费应用于盈利超过一定限额的商业应用中,详情见官网IdentityServer。微软同时也在将广泛使用的IdentityServer的相关功能逐步集成到框架中:ASP.NET Core 6 and Authentication Servers,在本文中同样暂不会涉及。

实现

引入Identity组件

我们在Infrastructure项目中添加以下Nuget包:

<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.1" /><PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.1" />

并新建Identity目录用于存放有关认证和授权的具体功能,首先添加用户类ApplicationUser

Applicatio

引入认证授权中间件

Api项目的Program中,MapControllers上面引入:

Program.cs

// 省略其他...app.UseAuthentication();app.UseAuthorization();

添加JWT配置

appsettings.Development.json

"JwtSettings": {    "validIssuer": "TodoListApi",    "validAudience": "http://localhost:5050",    "expires": 5}

增加认证用户Model

Application/Common/Models中添加用于用户认证的类型:

UserForAuthentication.cs

using System.ComponentModel.DataAnnotations;namespace TodoList.Application.Common.Models;public record UserForAuthentication{    [Required(ErrorMessage = "username is required")]    public string? UserName { get; set; }    [Required(ErrorMessage = "password is required")]    public string? Password { get; set; }}

实现认证服务CreateToken方法

因为本篇文章我们没有使用集成的IdentityServer组件,而是应用程序自己去发放Token,那就需要我们去实现CreateTokenAsync方法:

IdentityService.cs

// 省略其他...public async Task<string> CreateTokenAsync(){    var signingCredentials = GetSigningCredentials();    var claims = await GetClaims();    var tokenOptions = GenerateTokenOptions(signingCredentials, claims);    return new JwtSecurityTokenHandler().WriteToken(tokenOptions);}private SigningCredentials GetSigningCredentials(){    // 出于演示的目的,我将SECRET值在这里fallback成了硬编码的字符串,实际环境中,最好是需要从环境变量中进行获取,而不应该写在代码中    var key = Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable("SECRET") ?? "TodoListApiSecretKey");    var secret = new SymmetricSecurityKey(key);    return new SigningCredentials(secret, SecurityAlgorithms.HmacSha256);}private async Task<List<Claim>> GetClaims(){    // 演示了返回用户名和Role两类Claims    var claims = new List<Claim>    {        new(ClaimTypes.Name, User!.UserName)    };    var roles = await _userManager.GetRolesAsync(User);    claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));    return claims;}private JwtSecurityToken GenerateTokenOptions(SigningCredentials signingCredentials, List<Claim> claims){    // 配置JWT选项    var jwtSettings = _configuration.GetSection("JwtSettings");    var tokenOptions = new JwtSecurityToken    (        jwtSettings["validIssuer"],        jwtSettings["validAudience"],        claims,        expires: DateTime.Now.AddMinutes(Convert.ToDouble(jwtSettings["expires"])),        signingCredentials: signingCredentials    );    return tokenOptions;}

添加认证接口

Api项目中新建一个Controller用于实现获取Token的接口:

AuthenticationController.cs

using Microsoft.AspNetCore.Mvc;using TodoList.Application.Common.Interfaces;using TodoList.Application.Common.Models;namespace TodoList.Api.Controllers;[ApiController]public class AuthenticationController : ControllerBase{    private readonly IIdentityService _identityService;    private readonly ILogger<AuthenticationController> _logger;    public AuthenticationController(IIdentityService identityService, ILogger<AuthenticationController> logger)    {        _identityService = identityService;        _logger = logger;    }    [HttpPost("login")]    public async Task<IActionResult> Authenticate([FromBody] UserForAuthentication userForAuthentication)    {        if (!await _identityService.ValidateUserAsync(userForAuthentication))        {            return Unauthorized();        }        return Ok(new { Token = await _identityService.CreateTokenAsync() });    }}

保护API资源

我们准备使用创建TodoList接口来演示认证和授权功能,所以添加属性如下:

// 省略其他...[HttpPost]// 演示使用Policy的授权[Authorize(Policy = "OnlyAdmin")][ServiceFilter(typeof(LogFilterAttribute))]public async Task<ApiResponse<Domain.Entities.TodoList>> Create([FromBody] CreateTodoListCommand command){    return ApiResponse<Domain.Entities.TodoList>.Success(await _mediator.Send(command));}

验证

验证1: 验证直接访问创建TodoList接口

启动Api项目,直接执行创建TodoList的请求:

.NET 6实现基于JWT的Identity功能方法详解

得到了401 Unauthorized结果。

验证2: 获取Token

请求获取Token的接口:

.NET 6实现基于JWT的Identity功能方法详解

可以看到我们已经拿到了JWT Token,把这个Token放到JWT解析一下可以看到:

.NET 6实现基于JWT的Identity功能方法详解

主要在payload中可以看到两个Claims和其他配置的信息。

验证3: 携带Token访问创建TodoList接口

选择Bearer Token验证方式并填入获取到的Token,再次请求创建TodoList:

.NET 6实现基于JWT的Identity功能方法详解

验证4: 更换Policy

修改Infrastructure/DependencyInjection.cs

// 省略其他...// 添加授权Policy是基于角色的,策略名称为OnlyAdmin,策略要求具有Administrator角色services.AddAuthorization(options =>{    options.AddPolicy("OnlyAdmin", policy => policy.RequireRole("Administrator"));    options.AddPolicy("OnlySuper", policy => policy.RequireRole("SuperAdmin"));});

并修改创建TodoList接口的授权Policy:

// 省略其他...[Authorize(Policy = "OnlySuper")]

还是使用admin@locahost用户的用户名和密码获取最新的Token后,携带Token请求创建新的TodoList

.NET 6实现基于JWT的Identity功能方法详解

得到了403 Forbidden返回,并且从日志中我们可以看到:

.NET 6实现基于JWT的Identity功能方法详解

告诉我们需要一个具有SuperAdmin角色的用户的合法Token才会被授权。

那么到此为止,我们已经实现了基于.NET自带的Identity框架,发放Token,完成认证和授权的功能。

一点扩展

关于在.NET Web API项目中进行认证和授权的主题非常庞大,首先是认证的方式可以有很多种,除了我们在本文中演示的基于JWT Token的认证方式以外,还有OpenId认证,基于Azuwww.easck.comre Active Directory的认证,基于OAuth协议的认证等等;其次是关于授权的方式也有很多种,可以是基于角色的授权,可以是基于Claims的授权,可以是基于Policy的授权,也可以自定义更多的授权方式。然后是具体的授权服务器的实现,有基于Identity Server 4的实现,当然在其更改过协议后,我们可以转而使用.NET中移植进来的IdentityServer组件实现,配置的方式也有很多。

由于IdentityServer涉及的知识点过于庞杂,所以本文并没有试图全部讲到,考虑后面单独出一个系列来讲关于IdentityServer.NET 6 Web API开发中的应用。

总结

在本文中,我们实现了基于JWT Token的认证和授权。下一篇文章我们来看看为什么需要以及如何实现Refresh Token机制。

参考资料

IdentityServer

ASP.NET Core 6 and Authentication Servers

以上就是.NET 6实现基于JWT的Identity功能方法详解的详细内容,更多关于.NET 6基于JWT的Identity功能的资料请关注我们其它相关文章!