• 造轮子之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 Claims { get; set; }
            public virtual ICollection Logins { get; set; }
            public virtual ICollection Tokens { get; set; }
            public virtual ICollection? UserRoles { get; set; }
        }
    }
    
    namespace Wheel.Domain.Identity
    {
        public class Role : IdentityRole, IEntity<string>
        {
            /// 
            /// 角色类型,0管理台角色,1客户端角色
            /// 
            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 UserRoles { get; set; }
            public virtual ICollection 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()
                                .Extensions.OfType()
                                .FirstOrDefault()?.ApplicationServiceProvider
                                ?.GetService>()
                                ?.Value?.Stores;
    
            public WheelDbContext(DbContextOptions 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(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(b =>
                {
                    b.HasKey(uc => uc.Id);
                    b.ToTable("UserClaims");
                });
                builder.Entity(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(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(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(b =>
                {
                    b.HasKey(rc => rc.Id);
                    b.ToTable("RoleClaims");
                });
    
                builder.Entity(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
    

    执行完命令后我们连接数据库即可看到表成功创建。
    image.png

    配置Identity#

    在Program中添加下面代码:

    builder.Services.AddIdentityCore()
                    .AddRoles()
                    .AddEntityFrameworkStores()
                    .AddApiEndpoints();
    

    这里指定了Identity用户类型以及角色类型,并且指定EF操作的DbContext。
    AddApiEndpoints则是注入WebAPI所需的服务,我们F12进去可以看到里面的配置。

    /// 
    /// Adds configuration and services needed to support 
    /// but does not configure authentication. Call  and/or
    ///  to configure authentication separately.
    /// 
    /// The .
    /// The .
    public static IdentityBuilder AddApiEndpoints(this IdentityBuilder builder)
    {
        ArgumentNullException.ThrowIfNull(builder);
    
        builder.AddSignInManager();
        builder.AddDefaultTokenProviders();
        builder.Services.TryAddTransient();
        builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, IdentityEndpointsJsonOptionsSetup>());
        return builder;
    }
    

    接下来就是配置API了,在中间件中添加MapIdentityApi:

    app.MapGroup("api/identity")
       .WithTags("Identity")
       .MapIdentityApi();
    

    这里需要注意的是,如果不先MapGroup,则我们的请求路径只直接从/开始的,MapGroup("api/identity")则是指定从/api/identity开始。WithTags则是指定我们Swagger生成API的Tag显示名称。
    下面两图可以看到区别:
    image.png
    image.png
    直接调用register和login方法即可完成注册登录,这里只贴上一个登录返回的截图,可以看到我们成功拿到了accessToken以及refreshToken。
    image.png
    使用Post带上token请求/api/identity/manage/info。成功拿到用户信息。
    image.png

    这样我们就轻轻松松完成了asp.net core identity对WebApi的集成了。

    轮子仓库地址https://github.com/Wheel-Framework/Wheel
    欢迎进群催更。

    image.png

  • 相关阅读:
    剑指Offer面试题解总结51~60
    Java线程池是如何保证核心线程不被销毁的
    编写内联函数求解 2x²+4x+5的值,并用主函数调用该函数
    前端工作总结212-账号绑定逻辑复杂
    ORA-1142 signalled during: ALTER DATABASE END BACKUP...
    Easyrecovery靠谱不收费的数据恢复电脑软件
    【NR技术】 3GPP支持无人机服务的关键性能指标
    亚马逊FBA头程物流运输方式有哪些?
    部署百川大语言模型Baichuan2
    SpringBoot项目--电脑商城【用户注册】
  • 原文地址:https://www.cnblogs.com/fanshaoO/p/17751027.html