返回顶部
首页 > 资讯 > 后端开发 > ASP.NET >.NET6实现基于JWT的Identity功能方法详解
  • 461
分享到

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

2024-04-02 19:04:59 461人浏览 薄情痞子
摘要

目录需求目标原理与思路实现引入Identity组件添加认证服务使用Jwt认证和定义授权方式引入认证授权中间件添加JWT配置增加认证用户Model实现认证服务CreateToken方法

需求

.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

ApplicationUser.cs

using Microsoft.AspNetCore.Identity;

namespace TodoList.Infrastructure.Identity;

public class ApplicationUser : IdentityUser
{
    // 不做定制化实现,仅使用原生功能
}

由于我们希望使用现有的SQL Server数据库来存储认证相关的信息,所以还需要修改DbContext:

TodoListDbContext.cs

public class TodoListDbContext : IdentityDbContext<ApplicationUser>
{
    private readonly IDomainEventService _domainEventService;

    public TodoListDbContext(
        DbContextOptions<TodoListDbContext> options,
        IDomainEventService domainEventService) : base(options)
    {
        _domainEventService = domainEventService;
    }
    // 省略其他...
}

为了后面演示的方便,我们还可以在添加种子数据的逻辑里增加内置用户数据:

TodoListDbContextSeed.cs

// 省略其他...
public static async Task SeedDefaultUserAsync(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager)
{
    var administratorRole = new IdentityRole("Administrator");
    if (roleManager.Roles.All(r => r.Name != administratorRole.Name))
    {
        await roleManager.CreateAsync(administratorRole);
    }
    var administrator = new ApplicationUser { UserName = "admin@localhost", Email = "admin@localhost" };
    if (userManager.Users.All(u => u.UserName != administrator.UserName))
    {
        // 创建的用户名为admin@localhost,密码是admin123,角色是Administrator
        await userManager.CreateAsync(administrator, "admin123");
        await userManager.AddToRolesAsync(administrator, new[] { administratorRole.Name });
    }
}

并在ApplicationStartupExtensions中修改:

ApplicationStartupExtensions.cs

public static class ApplicationStartupExtensions
{
    public static async Task MigrateDatabase(this WebApplication app)
    {
        using var scope = app.Services.CreateScope();
        var services = scope.ServiceProvider;

        try
        {
            var context = services.GetRequiredService<TodoListDbContext>();
            context.Database.Migrate();

            var userManager = services.GetRequiredService<UserManager<ApplicationUser>>();
            var roleManager = services.GetRequiredService<RoleManager<IdentityRole>>();
            // 生成内置用户
            await TodoListDbContextSeed.SeedDefaultUserAsync(userManager, roleManager);
            // 省略其他...
        }
        catch (Exception ex)
        {
            throw new Exception($"An error occurred migrating the DB: {ex.Message}");
        }
    }
}

最后我们需要来修改DependencyInjection部分,以引入身份认证和授权服务:

DependencyInjection.cs

// 省略其他....
// 配置认证服务
// 配置认证服务
services
    .ADDDefaultIdentity<ApplicationUser>(o =>
    {
        o.PassWord.RequireDigit = true;
        o.Password.RequiredLength = 6;
        o.Password.RequireLowercase = true;
        o.Password.RequireUppercase = false;
        o.Password.RequireNonAlphanumeric = false;
        o.User.RequireUniqueEmail = true;
    })
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<TodoListDbContext>()
    .AddDefaultTokenProviders();

添加认证服务

Applicaiton/Common/Interfaces中添加认证服务接口IIdentityService

IIdentityService.cs

namespace TodoList.Application.Common.Interfaces;

public interface IIdentityService
{
    // 出于演示的目的,只定义以下方法,实际使用的认证服务会提供更多的方法
    Task<string> CreateUserAsync(string userName, string password);
    Task<bool> ValidateUserAsync(UserForAuthentication userForAuthentication);
    Task<string> CreateTokenAsync();
}

然后在Infrastructure/Identity中实现IIdentityService接口:

IdentityService.cs

namespace TodoList.Infrastructure.Identity;

public class IdentityService : IIdentityService
{
    private readonly ILogger<IdentityService> _logger;
    private readonly IConfiguration _configuration;
    private readonly UserManager<ApplicationUser> _userManager;
    private ApplicationUser? User;

    public IdentityService(
        ILogger<IdentityService> logger,
        IConfiguration configuration,
        UserManager<ApplicationUser> userManager)
    {
        _logger = logger;
        _configuration = configuration;
        _userManager = userManager;
    }

    public async Task<string> CreateUserAsync(string userName, string password)
    {
        var user = new ApplicationUser
        {
            UserName = userName,
            Email = userName
        };

        await _userManager.CreateAsync(user, password);

        return user.Id;
    }

    public async Task<bool> ValidateUserAsync(UserForAuthentication userForAuthentication)
    {
        User = await _userManager.FindByNameAsync(userForAuthentication.UserName);

        var result = User != null && await _userManager.CheckPasswordAsync(User, userForAuthentication.Password);
        if (!result)
        {
            _logger.LogWarning($"{nameof(ValidateUserAsync)}: Authentication failed. Wrong username or password.");
        }

        return result;
    }

    public async Task<string> CreateTokenAsync()
    {
        // 暂时还不来实现这个方法
        throw new NotImplementedException();
    }
}

并在DependencyInjection中进行依赖注入:

DependencyInjection.cs

// 省略其他...
// 注入认证服务
services.AddTransient<IIdentityService, IdentityService>();

现在我们来回顾一下已经完成的部分:我们配置了应用程序使用内建的Identity服务并使其使用已有的数据库存储;我们生成了种子用户数据;还实现了认证服务的功能。

在继续下一步之前,我们需要对数据库做一次Migration,使认证鉴权相关的数据表生效:

$ dotnet ef database update -p src/TodoList.Infrastructure/TodoList.Infrastructure.csproj -s src/TodoList.Api/TodoList.Api.csproj
Build started...
Build succeeded.
[14:04:02 INF] Entity Framework Core 6.0.1 initialized 'TodoListDbContext' using provider 'Microsoft.EntityFrameworkCore.sqlServer:6.0.1' with options: MigrationsAssembly=TodoList.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
# 创建相关数据表...
[14:04:03 INF] Executed DbCommand (43ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE [AspNetRoles] (
    [Id] nvarchar(450) NOT NULL,
    [Name] nvarchar(256) NULL,
    [NORMalizedName] nvarchar(256) NULL,
    [ConcurrencyStamp] nvarchar(max) NULL,
    CONSTRAINT [PK_AspNetRoles] PRIMARY KEY ([Id])
);
# 省略中间的部分..
[14:04:03 INF] Executed DbCommand (18ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20220108060343_AddIdentities', N'6.0.1');
Done.

运行Api程序,然后去数据库确认一下生成的数据表:

种子用户:

以及角色:

到目前为止,我已经集成了Identity框架,接下来我们开始实现基于JWT的认证和API的授权功能:

使用JWT认证和定义授权方式

Infrastructure项目的DependencyInjection中添加JWT认证配置:

DependencyInjection.cs

// 省略其他...
// 添加认证方法为JWT Token认证
services
    .AddAuthentication(opt =>
    {
        opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,

            ValidIssuer = configuration.GetSection("JwtSettings")["validIssuer"],
            ValidAudience = configuration.GetSection("JwtSettings")["validAudience"],
            // 出于演示的目的,我将SECRET值在这里fallback成了硬编码的字符串,实际环境中,最好是需要从环境变量中进行获取,而不应该写在代码中
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable("SECRET") ?? "TodoListApiSecreTKEy"))
        };
    });

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

引入认证授权中间件

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的请求:

得到了401 Unauthorized结果。

验证2: 获取Token

请求获取Token的接口:

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

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

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

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

验证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

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

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

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

一点扩展

关于在.NET Web API项目中进行认证和授权的主题非常庞大,首先是认证的方式可以有很多种,除了我们在本文中演示的基于JWT Token的认证方式以外,还有OpenId认证,基于Azure 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功能的资料请关注编程网其它相关文章!

--结束END--

本文标题: .NET6实现基于JWT的Identity功能方法详解

本文链接: https://lsjlt.com/news/162126.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • .NET6实现基于JWT的Identity功能方法详解
    目录需求目标原理与思路实现引入Identity组件添加认证服务使用JWT认证和定义授权方式引入认证授权中间件添加JWT配置增加认证用户Model实现认证服务CreateToken方法...
    99+
    2024-04-02
  • 基于GORM实现CreateOrUpdate方法详解
    目录正文GORM 写接口原理CreateSaveUpdate & UpdatesFirstOrInitFirstOrCreate方案一:FirstOrCreate ...
    99+
    2024-04-02
  • 基于C#实现语音识别功能详解
    在.NET4.0中,我可以借助System.Speech组件让电脑来识别我们的声音。 以上,当我说"name",显示"Darren",我说&...
    99+
    2024-04-02
  • 如何基于JWT实现接口的授权访问详解
    目录 什么是JWTJWT的结构HeaderPayloadSignature解码后的JWTJWT是怎样工作的在JAVA里使用JWT引入依赖JWTService生成JWT解码J...
    99+
    2024-04-02
  • 基于Vue实现Excel解析与导出功能详解
    目录前言基本介绍 代码实现 基本结构 上传解析Excel的导出 基本结构 导出Excel 总结前言 最近在整理日常开发中长涉及到的业务需求,正好想到了excel的解析与上传方面的事情...
    99+
    2024-04-02
  • 基于Python实现评论区抽奖功能详解
    目录1. 分析评论接口2. 获取评论数据3. 筛选评论用户4. 抽取幸运观众5. 完整源码5.1 字符串截取的方式5.2 正则匹配方式5.3 执行结果1. 分析评论接口 首先,我们需...
    99+
    2024-04-02
  • 基于redis实现的点赞功能设计思路详解
    前言 点赞其实是一个很有意思的功能。基本的设计思路有大致两种, 一种自然是用mysql等 数据库直接落地存储, 另外一种就是利用点赞的业务特征来扔到redis(或memcache)中, 然后离线刷回mysq...
    99+
    2022-06-04
    详解 思路 功能设计
  • 基于C#实现Windows服务的方法详解
    目录前言Windows服务介绍创建Windows服务安装卸载Windows服务前言 在实际应用过程中,有时候我们希望开发的程序,不需要界面,直接开机就可以长时间运行,这时候,我们可以...
    99+
    2024-04-02
  • recorder.js基于Html5录音功能的实现方法是什么
    这篇文章主要讲解了“recorder.js基于Html5录音功能的实现方法是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“recorder.js基于Ht...
    99+
    2024-04-02
  • Android基于Http协议实现文件上传功能的方法
    本文实例讲述了Android基于Http协议实现文件上传功能的方法。分享给大家供大家参考,具体如下: 注意一般使用Http协议上传的文件都比较小,一般是小于2M 这里示例是上传...
    99+
    2022-06-06
    HTTP 方法 文件上传 http协议 Android
  • MyBatisPlus+Lombok实现分页功能的方法详解
    目录一、Lombok1、添加Lombok依赖2、安装Lombok插件3、模型类上添加注解二、分页功能1、调用方法传入参数获取返回值2、设置分页拦截器3、运行测试程序一、Lombok ...
    99+
    2024-04-02
  • 基于Python实现配置热加载的方法详解
    目录背景如何实现使用多进程实现配置热加载使用signal信号量来实现热加载采用multiprocessing.Event 来实现配置热加载结语背景 由于最近工作需求,需要在...
    99+
    2024-04-02
  • SpringBoot集成JWT实现登陆验证的方法详解
    1:首先,我们需要在项目中导入两个依赖: <dependency> <groupId>com.auth0</gr...
    99+
    2024-04-02
  • Python实现邮件发送功能的方法详解
    目录利用 python 发送普通邮件认识发送邮件流程认识邮件协议smtplib 模块email 包发送邮件小案例发送邮件的避坑总结邮件自动化篇章所需的新模块: smtplib 邮件协...
    99+
    2024-04-02
  • SpringCloud实现文件上传功能的方法详解
    目录图片上传搭建项目编写上传功能图片上传 刚才的新增实现中,我们并没有上传图片,接下来我们一起完成图片上传逻辑。 文件的上传并不只是在品牌管理中有需求,以后的其它服务也可能需要,因此...
    99+
    2024-04-02
  • Android开发之图片旋转功能实现方法【基于Matrix】
    本文实例讲述了Android开发之图片旋转功能实现方法。分享给大家供大家参考,具体如下:在Android中进行图像旋转需要使用Matrix,它包含了一个3*3的矩阵,专门用于进行图像变换匹配。Matrix ,中文里叫矩阵,高等数学里有介绍,...
    99+
    2023-05-30
    android 图片 旋转
  • Java JWT实现跨域身份验证方法详解
    目录1、JWT简介2、JWT的结构2.1 头部(header)2.2 载荷(payload)2.3 签证(signature)3、JWT的原则4、JWT的用法5、JWT的问题和趋势6...
    99+
    2024-04-02
  • 基于python实现垂直爬虫系统的方法详解
    html_downloader from urllib import request def download(url): if url is None: r...
    99+
    2024-04-02
  • JavaScript基于定时器实现图片无缝滚动功能的方法
    这篇文章主要介绍了JavaScript基于定时器实现图片无缝滚动功能的方法,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。具体如下:一、无缝滚...
    99+
    2024-04-02
  • 基于websocket的聊天功能怎么实现
    本篇内容主要讲解“基于websocket的聊天功能怎么实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“基于websocket的聊天功能怎么实现”吧!   一...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作