.NET 6 集成 IdentityServer4+AspNetCore Identity 读取本地数据表用户

前言

IdentityServer4 实现鉴权、授权,AspNetCore Identity实现数据库用户管理表直接生成。

ps:IdentityServer4文档上最后给的例子是 // 配置使用内存存储用户信息,但使用 EF 存储客户端和资源信息,

我初步要实现的是 //数据库存储用户信息  内存存储资源  (下一步资源也放数据库 以后弄好了有机会更).

一、创建.NET 6 API 程序,一顿引用包括

.NET 6 集成 IdentityServer4+AspNetCore Identity 读取本地数据表用户

防止图片挂掉打一遍文字:

  • IdentityServer4

  • IdengtityServer4.AspNetIdentity

  • AspNetCore.Identity.EntityFrameWorkCore(生成数据库表用的)

  • EntityFrameWork+Disign+Tool三件套 (缺了不能自动迁移)

  • Pomelo.EntityFrameWorkCore.MySql(我是用的MySql,如果是SqlServer 不用这个用一个大概叫EF.Sqlserver的)

  • Encrypt (加密MD5用的 不必须)

下面那个是自带的。

二、建立数据库连接类

public class IdpDbContext : IdentityDbContext<ApplicationUser>
{
    public IdpDbContext(DbContextOptions<IdpDbContext> opt) : base(opt)
    {

    }
    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        builder.Entity<ApplicationUser>().ToTable("ApplicationUsers");
        #region #
        //builder.Entity<IdentityUserLogin<string>>().ToTable("ApplicationLogins");
        //builder.Entity<IdentityUserClaim<string>>().ToTable("ApplicationUserClaims");
        //builder.Entity<ApplicationUserRole>().ToTable("ApplicationUserRoles");
        //builder.Entity<IdentityUserToken<string>>().ToTable("ApplicationUserTokens");
        //builder.Entity<ApplicationRole>().ToTable("ApplicationRoles");
        //builder.Entity<IdentityRoleClaim<string>>().ToTable("ApplicationRoleClaims");
        //builder.Entity<ApplicationUserRole>().HasKey(t => t.Id).HasName("PK_UserRole_ID_KEY");
        #endregion

        builder.Entity<ApplicationUser>().HasData(
            new ApplicationUser()
            {
                Id = Guid.NewGuid().ToString(),
                RealName = "alice1",
                UserName = "alice1",
                PasswordHash = "alice1"
            });
        #region 初始化用戶与角色的种子数据
        //1. 更新用戶与角色的外鍵
        builder.Entity<ApplicationUser>(
            u => u.HasMany(x => x.UserRoles).WithOne().HasForeignKey(ur => ur.UserId).IsRequired()
            );
        //2. 添加管理员角色
        var adminRoleId = "f8df1775-e889-46f4-acdd-421ec8d9ba64";
        builder.Entity<IdentityRole>().HasData(
            new IdentityRole()
            {
                Id = adminRoleId,
                Name = "Admin",
                NormalizedName = "Admin".ToUpper()
            }
        );
        //3. 添加用户
        var adminUserId = "f8df1775-e889-46f4-acdd-421ec8d9ba65";
        ApplicationUser adminUser = new ApplicationUser
        {
            Id = adminUserId,
            UserName = "admin",
            NormalizedUserName= "admin".ToUpper(),
            RealName = "admin",
            NormalizedEmail = "admin@qq.com".ToUpper(),
            Email = "admin@qq.com",
            TwoFactorEnabled = false,
            EmailConfirmed = true,
            PhoneNumber = "123456789",
            PhoneNumberConfirmed = false,

        };
        MyPasswordHasher ph = new MyPasswordHasher();
        adminUser.PasswordHash = ph.HashPassword(adminUser, "123456");
        builder.Entity<ApplicationUser>().HasData(adminUser);
        //4. 给用户加入管理员角色
        builder.Entity<IdentityUserRole<string>>().HasData(
            new IdentityUserRole<string>()
            {
                RoleId = adminRoleId,
                UserId = adminUserId
            }
            );
        #endregion

    }
}

三、Program里开始加东西(如果是历史的Net版本,是在StartUp里)

直接代码

using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using MyIDP;
using MyIDP.Models;
using MyIDP.Permission;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

//由此重要
builder.Services.AddDbContext<IdpDbContext>(opt =>
{
    opt.UseMySql("server=127.0.0.1;Port=3306;database=AccountDb;uid=root;pwd=123456;", new MySqlServerVersion(new Version(8,0,29)));
});

builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
                .AddUserManager<MyUserManager>()
                .AddEntityFrameworkStores<IdpDbContext>()
                .AddDefaultTokenProviders();

builder.Services.AddIdentityServer()
    .AddDeveloperSigningCredential()

    .AddInMemoryIdentityResources(MyIDP.IdpConfig.GetIdentityResources())
    .AddInMemoryClients(MyIDP.IdpConfig.GetClients())
    .AddInMemoryApiScopes( MyIDP.IdpConfig.GetScope())
    .AddInMemoryApiResources( MyIDP.IdpConfig.GetApiResources())    //.AddResourceOwnerValidator<MyResourceOwnerPasswordValidator>() //这句可以打开自主验证登录用户
    //.AddProfileService<MyProfileService>()
    .AddAspNetIdentity<ApplicationUser>()
    //.AddTestUsers(new List<IdentityServer4.Test.TestUser>
    //{
    //    new IdentityServer4.Test.TestUser
    //    {
    //        SubjectId="123",
    //        Username = "alice",
    //        Password = "alice",
    //        Claims = new List<Claim>() {
    //            new Claim(JwtClaimTypes.Role, "superadmin"),
    //            new Claim(JwtClaimTypes.Role, "admin")
    //        }
    //    }
    //})
    ;

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseIdentityServer();
app.UseAuthorization();
app.MapControllers();
app.Run();

因为使用的是内存储存t鉴权信息的方式,所以建立IdentityServer4的配置类IdpConfig

public static class IdpConfig
{
    public static IEnumerable<IdentityResource> GetIdentityResources()
    {
        return new IdentityResource[]
        {
            new IdentityResources.OpenId(),
            new IdentityResources.Profile(),
            new IdentityResources.Address(),
            new IdentityResources.Phone(),
            new IdentityResources.Email()
        };
    }

    public static IEnumerable<ApiResource> GetApiResources()
    {
        //return new ApiResource[]
        //{
        //    new ApiResource("api1", "My API #1",new List<string>(){JwtClaimTypes.Role})
        //};
        //新写法
        return new[]
        {
            new ApiResource("api1", "My API #1")
            {
                Scopes = { "scope1"}
            }
        };
    }

    public static IEnumerable<Client> GetClients()
    {
        return new[]
        {
            #region MyRegion
             //// client credentials flow client
            //new Client
            //{
            //    ClientId = "console client",
            //    ClientName = "Client Credentials Client",

            //    AllowedGrantTypes = GrantTypes.ClientCredentials,

            //    ClientSecrets = { new Secret("511536EF-F270-4058-80CA-1C89C192F69A".Sha256()) },

            //    AllowedScopes = { "api1" }
            //},

#endregion
           
            // wpf client, password grant
            new Client
            {
                ClientId = "client",
                AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
                ClientSecrets =
                {
                    new Secret("secret".Sha256())
                },
                AllowedScopes = //允许当访问的资源
                {
                    "scope1",
                    //"api1",
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Email,
                    IdentityServerConstants.StandardScopes.Address,
                    IdentityServerConstants.StandardScopes.Phone,
                    IdentityServerConstants.StandardScopes.Profile }
            }
        };
    }

    public static IEnumerable<ApiScope> GetScope()
    {
        return new ApiScope[] {
            new ApiScope("scope1"),
            new ApiScope("scope2"),
        };
    }
}

数据库的usernamager

public class MyUserManager : UserManager<ApplicationUser>
{
    public MyUserManager(IUserStore<ApplicationUser> store, IOptions<IdentityOptions> optionsAccessor, IPasswordHasher<ApplicationUser> passwordHasher,
      IEnumerable<IUserValidator<ApplicationUser>> userValidators, IEnumerable<IPasswordValidator<ApplicationUser>> passwordValidators, ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, IServiceProvider services, ILogger<UserManager<ApplicationUser>> logger)
       : base(store, optionsAccessor, new MyPasswordHasher(), userValidators, passwordValidators, keyNormalizer, errors, services, logger)
    {
        optionsAccessor.Value.Password.RequireDigit = false;
        optionsAccessor.Value.Password.RequiredLength = 4;
        optionsAccessor.Value.Password.RequireLowercase = false;
        optionsAccessor.Value.Password.RequireUppercase = false;
        optionsAccessor.Value.Password.RequireNonAlphanumeric = false;
    }

}

重写验证密码的方法类MyResourceOwnerPasswordValidator,(如果没有打开Program中的AddResourceOwnerValidator< MyResourceOwnerPasswordValidator>() 则不需要)

public class MyResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
    public readonly SignInManager<ApplicationUser> signInManager;
    private readonly MyUserManager userManager;
    //public readonly IEventService service;
    public MyResourceOwnerPasswordValidator(MyUserManager userService, SignInManager<ApplicationUser> signInManager)//, IEventService service)
    {
        userManager = userService;
        this.signInManager = signInManager;
        //this.service = service;

    }
    public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
    {
        if (string.IsNullOrEmpty(context.UserName) || string.IsNullOrEmpty(context.Password))
        {
            context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "验证被拒绝,用户名或者密码为空。");
            return;
        }
        var user = await userManager.FindByNameAsync(context.UserName);
        if (user == null)
        {
            context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "验证失败,不存在当前用户。");
            return;
        }
        //检验用户密码(虽然我也不知道他的密码是采用什么加密方式得到的,但是我也不需要知道) 
        var passwordPass = await userManager.CheckPasswordAsync(user, context.Password);
        if (!passwordPass)
        {
            context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "验证失败,用户凭证错误");
            return;
        }
        else
        {
            try
            {
                await userManager.AddLoginAsync(user, new UserLoginInfo(user.Id, "", user.UserName));
            }
            catch (Exception ex)
            {
                ;
            }
            finally
            {
                context.Result = new GrantValidationResult(user.Id, GrantType.ResourceOwnerPassword, new List<Claim>() { new Claim("account", user.UserName) }); 
            }
        }
        return;
    }

}

MyPasswordHasher

public class MyPasswordHasher : PasswordHasher<ApplicationUser>
{
    public override string HashPassword(ApplicationUser user, string password)
    {
        //PasswordHasher<ApplicationUser> ph = new PasswordHasher<ApplicationUser>();
        //var pstr = ph.HashPassword(new ApplicationUser(), password);
        //return pstr;
        return password.MD5();
    }

    public override PasswordVerificationResult VerifyHashedPassword(ApplicationUser user, string hashedPassword, string providedPassword)
    {
        if (providedPassword.MD5().Equals(hashedPassword))
        {
            return PasswordVerificationResult.Success;
        }
        else
        {
            return PasswordVerificationResult.Failed;
        }
    }
}

创建自己的User类 ApplicationUser继承 IdentityUser 复写自带的AspNetUser表

public class ApplicationUser : IdentityUser
{
    public string MySomething { get; set; } = "";
    /// <summary>
    /// 创建时间
    /// </summary>
    public DateTime CreateTime { get; set; }

    /// <summary>
    /// 创建人Id
    /// </summary>
    public string CreatorId { get; set; } = "";

    /// <summary>
    /// 否已删除
    /// </summary>
    public bool Deleted { get; set; }
     

    /// <summary>
    /// 姓名
    /// </summary>
    public string RealName { get; set; }

    /// <summary>
    /// 性别
    /// </summary>
    public Sex Sex { get; set; }

    /// <summary>
    /// 出生日期
    /// </summary>
    public DateTime? Birthday { get; set; }

    /// <summary>
    /// 所属部门Id
    /// </summary>
    public string DepartmentId { get; set; } = "";

    public string OtherData { get; set; } = "";

    // 用户角色 用户权限 用户信息 用户登录tokens  重新绑定与父类的关系 命名必须和父类一致
    public virtual ICollection<IdentityUserRole<string>> UserRoles { get; set; }
    public virtual ICollection<IdentityUserClaim<string>> Claims { get; set; }
    public virtual ICollection<IdentityUserLogin<string>> Logins { get; set; }
    public virtual ICollection<IdentityUserToken<string>> Tokens { get; set; }
}

public enum Sex
{
    [Description("男")]
    Man = 1,

    [Description("女")]
    Woman = 0
}

至此可以生成数据库迁移后 Postman测试一下

.NET 6 集成 IdentityServer4+AspNetCore Identity 读取本地数据表用户