造轮子之asp.net core identity
在前面我们完成了应用最基础的功能支持以及数据库配置,接下来就是我们的用户角色登录等功能了,在asp.net core中原生Identity可以让我们快速完成这个功能的开发,在.NET8中,asp.net core identity支持了WebApi的注册登录。这让我们在WebApi中可以更爽快的使用。
安装包
首先我们需要安装Microsoft.AspNetCore.Identity.EntityFrameworkCore这个包来创建我们的数据库结构
创建实体
在asp.net core identity中默认包含了IdentityUser,IdentityRole,IdentityRoleClaim,IdentityUserClaim,IdentityUserLogin,IdentityUserRole,IdentityUserToken这几个基类,我们可以直接使用这些,也可以通过继承来灵活扩展我们的表结构。当然,可以按照约定不使用继承的方式,创建类添加必要的属性字段也可。
这里我们选择把所有的类都继承一遍,方便以后扩展。
namespace Wheel.Domain.Identity
{
    public class User : IdentityUser, IEntity<string>
    {
        public virtual DateTimeOffset CreationTime { get; set; }
        public virtual ICollection<UserClaim> Claims { get; set; }
        public virtual ICollection<UserLogin> Logins { get; set; }
        public virtual ICollection<UserToken> Tokens { get; set; }
        public virtual ICollection<UserRole>? UserRoles { get; set; }
    }
}
namespace Wheel.Domain.Identity
{
    public class Role : IdentityRole, IEntity<string>
    {
        /// <summary>
        /// 角色类型,0管理台角色,1客户端角色
        /// </summary>
        public RoleType RoleType { get; set; }
        public Role(string roleName, RoleType roleType) : base (roleName)
        {
            RoleType = roleType;
        }
        public Role(string roleName) : base (roleName)
        {
        }
        public Role() : base ()
        {
        }
        public virtual ICollection<UserRole> UserRoles { get; set; }
        public virtual ICollection<RoleClaim> RoleClaims { get; set; }
    }
}
这里主要展示一下User和Role,别的可自行查看代码仓库。
修改DbContext
在WheelDbContext继承IdentityDbContext,IdentityDbContext支持传入我们的泛型User,Role类型。
namespace Wheel.EntityFrameworkCore
{
    public class WheelDbContext : IdentityDbContext<User, Role, string, UserClaim, UserRole, UserLogin, RoleClaim, UserToken>
    {
        private StoreOptions? GetStoreOptions() => this.GetService<IDbContextOptions>()
                            .Extensions.OfType<CoreOptionsExtension>()
                            .FirstOrDefault()?.ApplicationServiceProvider
                            ?.GetService<IOptions<IdentityOptions>>()
                            ?.Value?.Stores;
        public WheelDbContext(DbContextOptions<WheelDbContext> options) : base(options)
        {
        }
        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
            ConfigureIdentity(builder);
        }
        void ConfigureIdentity(ModelBuilder builder)
        {
            var storeOptions = GetStoreOptions();
            var maxKeyLength = storeOptions?.MaxLengthForKeys ?? 0;
            builder.Entity<User>(b =>
            {
                b.HasKey(u => u.Id);
                b.HasIndex(u => u.NormalizedUserName).HasDatabaseName("UserNameIndex").IsUnique();
                b.HasIndex(u => u.NormalizedEmail).HasDatabaseName("EmailIndex");
                b.ToTable("Users");
                b.Property(u => u.ConcurrencyStamp).IsConcurrencyToken();
                b.Property(u => u.Id).HasMaxLength(36);
                b.Property(u => u.UserName).HasMaxLength(256);
                b.Property(u => u.NormalizedUserName).HasMaxLength(256);
                b.Property(u => u.Email).HasMaxLength(256);
                b.Property(u => u.NormalizedEmail).HasMaxLength(256);
                b.Property(u => u.CreationTime).HasDefaultValue(DateTimeOffset.Now);
                b.HasMany(e => e.Claims)
                    .WithOne(e => e.User)
                    .HasForeignKey(uc => uc.UserId)
                    .IsRequired();
                b.HasMany(e => e.Logins)
                    .WithOne(e => e.User)
                    .HasForeignKey(ul => ul.UserId)
                    .IsRequired();
                b.HasMany(e => e.Tokens)
                    .WithOne(e => e.User)
                    .HasForeignKey(ut => ut.UserId)
                    .IsRequired();
                b.HasMany(e => e.UserRoles)
                    .WithOne(e => e.User)
                    .HasForeignKey(ur => ur.UserId)
                    .IsRequired();
            });
            builder.Entity<UserClaim>(b =>
            {
                b.HasKey(uc => uc.Id);
                b.ToTable("UserClaims");
            });
            builder.Entity<UserLogin>(b =>
            {
                b.HasKey(l => new { l.LoginProvider, l.ProviderKey });
                if (maxKeyLength > 0)
                {
                    b.Property(l => l.LoginProvider).HasMaxLength(maxKeyLength);
                    b.Property(l => l.ProviderKey).HasMaxLength(maxKeyLength);
                }
                b.ToTable("UserLogins");
            });
            builder.Entity<UserToken>(b =>
            {
                b.HasKey(t => new { t.UserId, t.LoginProvider, t.Name });
                if (maxKeyLength > 0)
                {
                    b.Property(t => t.LoginProvider).HasMaxLength(maxKeyLength);
                    b.Property(t => t.Name).HasMaxLength(maxKeyLength);
                }
                b.ToTable("UserTokens");
            });
            builder.Entity<Role>(b =>
            {
                b.HasKey(r => r.Id);
                b.HasIndex(r => r.NormalizedName).HasDatabaseName("RoleNameIndex").IsUnique();
                b.ToTable("Roles");
                b.Property(u => u.Id).HasMaxLength(36);
                b.Property(r => r.ConcurrencyStamp).IsConcurrencyToken();
                b.Property(u => u.Name).HasMaxLength(256);
                b.Property(u => u.NormalizedName).HasMaxLength(256);
                b.HasMany(e => e.UserRoles)
                    .WithOne(e => e.Role)
                    .HasForeignKey(ur => ur.RoleId)
                    .IsRequired();
                b.HasMany(e => e.RoleClaims)
                    .WithOne(e => e.Role)
                    .HasForeignKey(rc => rc.RoleId)
                    .IsRequired();
            });
            builder.Entity<RoleClaim>(b =>
            {
                b.HasKey(rc => rc.Id);
                b.ToTable("RoleClaims");
            });
            builder.Entity<UserRole>(b =>
            {
                b.HasKey(r => new { r.UserId, r.RoleId });
                b.ToTable("UserRoles");
            });
        }
    }
}
执行数据库迁移命令
接下来我们使用VS的程序包管理器控制台。
使用命令创建和执行迁移文件:
Add-Migration Init
Update-Database
这里也可以使用Dotnet EF命令:
dotnet ef migrations add Init
dotnet ef database update
执行完命令后我们连接数据库即可看到表成功创建。
配置Identity
在Program中添加下面代码:
builder.Services.AddIdentityCore<User>()
                .AddRoles<Role>()
                .AddEntityFrameworkStores<WheelDbContext>()
                .AddApiEndpoints();
这里指定了Identity用户类型以及角色类型,并且指定EF操作的DbContext。
AddApiEndpoints则是注入WebAPI所需的服务,我们F12进去可以看到里面的配置。
/// <summary>
/// Adds configuration and services needed to support <see cref="IdentityApiEndpointRouteBuilderExtensions.MapIdentityApi{TUser}(IEndpointRouteBuilder)"/>
/// but does not configure authentication. Call <see cref="BearerTokenExtensions.AddBearerToken(AuthenticationBuilder, Action{BearerTokenOptions}?)"/> and/or
/// <see cref="IdentityCookieAuthenticationBuilderExtensions.AddIdentityCookies(AuthenticationBuilder)"/> to configure authentication separately.
/// </summary>
/// <param name="builder">The <see cref="IdentityBuilder"/>.</param>
/// <returns>The <see cref="IdentityBuilder"/>.</returns>
public static IdentityBuilder AddApiEndpoints(this IdentityBuilder builder)
{
    ArgumentNullException.ThrowIfNull(builder);
    builder.AddSignInManager();
    builder.AddDefaultTokenProviders();
    builder.Services.TryAddTransient<IEmailSender, NoOpEmailSender>();
    builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<JsonOptions>, IdentityEndpointsJsonOptionsSetup>());
    return builder;
}
接下来就是配置API了,在中间件中添加MapIdentityApi:
app.MapGroup("api/identity")
   .WithTags("Identity")
   .MapIdentityApi<User>();
这里需要注意的是,如果不先MapGroup,则我们的请求路径只直接从/开始的,MapGroup("api/identity")则是指定从/api/identity开始。WithTags则是指定我们Swagger生成API的Tag显示名称。
下面两图可以看到区别:

直接调用register和login方法即可完成注册登录,这里只贴上一个登录返回的截图,可以看到我们成功拿到了accessToken以及refreshToken。
使用Post带上token请求/api/identity/manage/info。成功拿到用户信息。
这样我们就轻轻松松完成了asp.net core identity对WebApi的集成了。
轮子仓库地址https://github.com/Wheel-Framework/Wheel
欢迎进群催更。

造轮子之asp.net core identity的更多相关文章
- IdentityServer(12)- 使用 ASP.NET Core Identity
		IdentityServer具有非常好的扩展性,其中用户及其数据(包括密码)部分你可以使用任何想要的数据库进行持久化. 如果需要一个新的用户数据库,那么ASP.NET Core Identity是你的 ... 
- ASP.NET Core Identity Hands On(1)——Identity 初次体验
		ASP.NET Core Identity是用于构建ASP.NET Core Web应用程序的成员资格系统,包括成员资格.登录和用户数据存储 这是来自于 ASP.NET Core Identity 仓 ... 
- ASP.NET Core Identity Hands On(2)——注册、登录、Claim
		上一篇文章(ASP.NET Core Identity Hands On(1)--Identity 初次体验)中,我们初识了Identity,并且详细分析了AspNetUsers用户存储表,这篇我们将 ... 
- IdentityServer4 中文文档 -14- (快速入门)使用 ASP.NET Core Identity
		IdentityServer4 中文文档 -14- (快速入门)使用 ASP.NET Core Identity 原文:http://docs.identityserver.io/en/release ... 
- IdentityServer4【QuickStart】之使用asp.net core Identity
		使用asp.net core Identity IdentityServer灵活的设计中有一部分是可以将你的用户和他们的数据保存到数据库中的.如果你以一个新的用户数据库开始,那么,asp.net co ... 
- ASP.NET Core Identity 实战(2)——注册、登录、Claim
		上一篇文章(ASP.NET Core Identity Hands On(1)--Identity 初次体验)中,我们初识了Identity,并且详细分析了AspNetUsers用户存储表,这篇我们将 ... 
- ASP.NET Core Identity 实战(4)授权过程
		这篇文章我们将一起来学习 Asp.Net Core 中的(注:这样描述不准确,稍后你会明白)授权过程 前情提要 在之前的文章里,我们有提到认证和授权是两个分开的过程,而且认证过程不属于Identity ... 
- 用一个应用场景理解ASP.NET Core Identity是什么?
		目录 前言 基于声明的认证(Claims-based Authentication) 应用场景一 在ASP.NET Core 中Identity是如何实现的 类ClaimsPrincipal 考察另外 ... 
- 用例子看ASP.NET Core Identity是什么?
		原文:用例子看ASP.NET Core Identity是什么? 目录 前言 基于声明的认证(Claims-based Authentication) Claim 在ASP.NET Core Iden ... 
- ASP.NET Core Identity 迁移数据 - ASP.NET Core 基础教程 - 简单教程,简单编程
		原文:ASP.NET Core Identity 迁移数据 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core Identity 迁移数据 上一章节中我们配置了 ... 
随机推荐
- 深度解读 Linux 内核级通用内存池 —— kmalloc 体系
			本文是笔者 slab 系列的最后一篇文章,为了方便大家快速检索,先将相关的文章列举出来: <细节拉满,80 张图带你一步一步推演 slab 内存池的设计与实现> <从内核源码看 sl ... 
- Pinot2的无人机创新和发展
			目录 1. 引言 2. 技术原理及概念 2.1 基本概念解释 2.2 技术原理介绍 2.3 相关技术比较 3. 实现步骤与流程 3.1 准备工作:环境配置与依赖安装 3.2 核心模块实现 3.3 集成 ... 
- GPT3的应用领域:机器翻译、文本生成、文本摘要
			目录 1. 引言 2. 技术原理及概念 3. 实现步骤与流程 3.1 准备工作:环境配置与依赖安装 3.2 核心模块实现 3.3 集成与测试 4. 应用示例与代码实现讲解 4.1 机器翻译 4.2 文 ... 
- 区块链应用:椭圆曲线数字签名算法ECDSA
			1 椭圆曲线密码学 椭圆曲线密码学(Elliptic Curve Cryptography,缩写ECC),是基于椭圆曲线数学理论实现的一种非对称加密算法.椭圆曲线在密码学中的使用是在1985年有Nea ... 
- Oracle随机生成大数据
			Oracle随机插入大数据 话不多说,安排 示例: -- 创建新表并批量插入五千万数据 create table TEST_TAB as select rownum as id, to_char(sy ... 
- 基于C#的无边框窗体动画效果的完美解决方案 - 开源研究系列文章
			最近在整理和编写基于C#的WinForm应用程序,然后碰到一个其他读者也可能碰到的问题,就是C#的Borderless无边框窗体的动画效果问题. 在Visual Studio 2022里,C#的Win ... 
- Linux 命令:rpm查询选项
			rpm(8) System Manager's Manual rpm(8) 名称 rpm - RPM 软件包管理器 查询选项 rpm的查询命令通常的格式如下: rpm -q [query-option ... 
- Python和PyTorch深入实现线性回归模型:一篇文章全面掌握基础机器学习技术
			1. 简介 1.1 线性回归模型概述 线性回归是一种统计学中的预测分析,该方法用于建立两种或两种以上变量间的关系模型.线性回归使用最佳的拟合直线(也称为回归线)在独立(输入)变量和因变量(输出)之间建 ... 
- 趣图|代码重构前vs重构后
			前言 很多程序员对自己写的代码平时很随心所欲,但当有一天让他维护他人的代码,他就会抓狂,很容易激发他体内重构的瘾.(大多数程序员审阅完别人代码后,先会忍不住吐槽一番,然后会忍不住想重构一把,) 在我看 ... 
- Git:多人写作时,如何保证代码一致性
			解决方案 git add . git commit -m "message" git pull origin develop # 拉取并合并dev分支上的代码 git push 
