IdentityServer4结合Mysql

 

上一篇:IdentityServer4使用OpenIdConnect实现单点登录

前面写的示例中,IdeneityServer使用的是内存缓存的存储方式,所有的配置都写在Config.cs里。在实际应用中,应该使用数据库存储方式,方便随时配置,如添加新的用户、资源、客户端,也可以节省服务器内存。

本文从三个方面来实现IdentityServer4结合Mysql实现数据库存储方式,分别是客户端及资源数据、令牌及授权码数据以及用户数据。

一,准备内容

1,准备MySql数据库服务器,新建一个空的数据库

2,IdentityServer需要安装以下几个程序包。

IdentityServer4.EntityFramework
Microsoft.EntityFrameworkCore.Tools
Pomelo.EntityFrameworkCore.MySql(也可以用MySql官方程序包:MySql.Data.EntityFrameworkCore)

3,appsettings.json添加数据库连接字符串

{
  "ConnectionStrings": {
    "MySqlDbConnectString": "server=IP;userid=mysqlUserName;pwd=user's password;database=database name;connectiontimeout=30;Pooling=true;Max Pool Size=300; Min Pool Size=5;"
  }
}  

二,客户端及资源的数据库存储

前面我们使用AddInMemory的方式加载配置数据

AddInMemoryIdentityResources(Config.GetIdentityResources())
AddInMemoryApiResources(Config.GetApis())
AddInMemoryClients(Config.GetClients())

把这三行代码注释掉,以下代码替换

  var connection = Configuration.GetConnectionString("MySqlDbConnectString");
            var builder = services.AddIdentityServer()
                //身份信息资源
                //.AddInMemoryIdentityResources(Config.GetIdentityResources())
                .AddConfigurationStore(opt =>
                {
                    opt.ConfigureDbContext = context =>
                    {
                        context.UseMySql(connection, sql =>
                        {
                            sql.MigrationsAssembly("IdentityServer");
                        });
                    };
                })
                .AddTestUsers(Config.GetUsers());}

要从数据库查询配置数据,肯定得添加相应的数据表,把IdentityServer项目设为启动项目,打开程序包管理器控制台,设置控制台默认项目为IdentityServer,在控制台输入以下指令

PM> add-migration ConfigDbContext -c ConfigurationDbContext  -o Data/Migrations/IdentityServer/PersistedGrantDb
To undo this action, use Remove-Migration.
PM> update-database
Done.

-c ConfigurationDbContext是指定当前的数据库上下文。ConfigurationDbContext定义在命名空间: IdentityServer4.EntityFramework.DbContexts,和昨们使用CodeFirst一样,定义了客户端及资源的数据表及表关联。add-migration生成迁移变动记录,update-datase更新数据库到最新状态。

 但是现在数据库中的客户端和资源数据都是为空的,需要把Config的配置数据添加到数据库,可以在Start.cs中添加方法进行数据库初始化

 private void InitializeDatabase(IApplicationBuilder app)
        {
            using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
            {
                var context = serviceScope.ServiceProvider.GetRequiredService<ConfigurationDbContext>();
                if (!context.Clients.Any())
                {
                    foreach (var client in Config.GetClients())
                    {
                        context.Clients.Add(client.ToEntity());
                    }
                    context.SaveChanges();
                }

                if (!context.IdentityResources.Any())
                {
                    foreach (var resource in Config.GetIdentityResources())
                    {
                        context.IdentityResources.Add(resource.ToEntity());
                    }
                    context.SaveChanges();
                }

                if (!context.ApiResources.Any())
                {
                    foreach (var resource in Config.GetApis())
                    {
                        context.ApiResources.Add(resource.ToEntity());
                    }
                    context.SaveChanges();
                }
            }
        }

说明:利用app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope创建一个新的服务作用域与其他作用域隔离开,不影响其它作用域的上下文释放,操作实体更新数据库和我们平时用的一样。运行程序后,发现数据库已经有数据了

 运行IdentityServer,IdentityMvc,IdentityApi三个程序进行测试

二,令牌和授权码的数据库存储

利用IIdentityServerBuilder的扩写方法AddOperationalStore

//客户端及资源数据库存储配置
                .AddConfigurationStore(opt =>
                {
                    opt.ConfigureDbContext = context =>
                    {
                        context.UseMySql(connection, sql =>
                        {
                            sql.MigrationsAssembly("IdentityServer");
                        });
                    };
                })
                //令牌及授权码数据库存储配置
                .AddOperationalStore(opt =>
                {
                    opt.ConfigureDbContext = context =>
                    {
                        context.UseMySql(connection, sql =>
                        {
                            sql.MigrationsAssembly("IdentityServer");
                        });
                    };
                    opt.EnableTokenCleanup = true;
                    opt.TokenCleanupInterval = 30;
                })
                .AddTestUsers(Config.GetUsers());

 同样的,需要添加用来存储的数据表

PM> add-migration OperationContext -c PersistedGrantDbContext -o Data/Migrations/IdentityServer/OperationDb
Multiple startup projects set.
PM> update-database -c PersistedGrantDbContext
Done.

  运行IdentityServer,IdentityMvc,IdentityApi三个程序进行测试

 三,用户的数据库存储

IdentityServer4为IIdentityServerBuilder提供了支持客户端和资源数据库存储的AddConfigurationStore方法,支持令牌和授权码数据库存储的AddOperationalStore,但没有提供用户数据库存储的方法。我想是因为客户端、资源、令牌、授权码的存储结构是强制规定的,而用户不是,每个人定义用户的资源、角色、权限、基础信息的存储结构等各不相同。不过没有关系,可以自己仿AddConfigurationStore写一个支持用户数据库存储的AddUserStore方法.

在IdentityServer项目新建一个名为IdentityUserStore的文件夹,创建四个类

IdentityServer.IdentityUserStore.IdentityUser:用户实体

public class IdentityUser
    {
        [Key]
        [Required]
        public string SubjectId { get; set; }
        [Required]
        public string Username { get; set; }
        [Required]
        public string Password { get; set; }
        public string ProviderName { get; set; }
        public string ProviderSubjectId { get; set; }
        public bool IsActive { get; set; }
     public ICollection<IdentityUserClaim> IdentityUserClaims { get; set; } } public class IdentityUserClaim { [Key] public string ClaimId { get; set; } [Required] public string Name { get; set; } [Required] public string Value { get; set; } [Required] public string UserSubjectId { get; set; } [ForeignKey("UserSubjectId")] public virtual IdentityUser IdentityUser { get; set; } }

IdentityServer.IdentityUserStore.UserStoreDbContext:用于定义数据库操作上下文,作用和前面使用过的ConfigurationDbContext、PersistedGrantDbContext一样

 public class UserStoreDbContext:DbContext
    {
        public UserStoreDbContext(DbContextOptions opt) : base(opt)
        {
            
        }
        public DbSet<IdentityUser> IdentityUser { get; set; }
        public DbSet<IdentityUserClaim> IdentityUserClaim { get; set; }
    }

IdentityServer.IdentityUserStore.UserStore:用于查询用户信息和验证登录密码

 public class UserStore 
    {
        private readonly UserStoreDbContext _dbContext;
        public UserStore(UserStoreDbContext dbContext)
        {
            _dbContext = dbContext;
        }
        /// <summary>
        /// 根据SubjectID查询用户信息
        /// </summary>
        /// <param name="subjectId">用户id</param>
        /// <returns></returns>
        public IdentityUser FindBySubjectId(string subjectId) {
            return _dbContext.Set<IdentityUser>().Where(r => r.SubjectId.Equals(subjectId)).Include(r => r.IdentityUserClaims).SingleOrDefault();
        }
        /// <summary>
        /// 根据用户名查询用户
        /// </summary>
        /// <param name="username">用户</param>
        /// <returns></returns>
        public IdentityUser FindByUsername(string username)
        {
            return _dbContext.Set<IdentityUser>().Where(r => r.Username.Equals(username)).Include(r => r.IdentityUserClaims).SingleOrDefault();
        }
        /// <summary>
        /// 验证登录密码
        /// </summary>
        /// <param name="username"></param>
        /// <param name="password"></param>
        /// <returns></returns>
        public bool ValidateCredentials(string username, string password)
        {
            password = Config.MD5Str(password);
            var user = _dbContext.Set<IdentityUser>().Where(r => r.Username.Equals(username)
            &&r.Password.Equals(password)).Include(r => r.IdentityUserClaims).SingleOrDefault();
            return user != null;
        }
    }

  IdentityServer.IdentityUserStore.IIdentityServerBuilderUserStoreExtensions:对IIdentityServerBuilder的扩写,添加UserStoreDbContext、UserStoreDbContext的服务依赖。

  public static class IIdentityServerBuilderUserStoreExtensions
    {
        public static IIdentityServerBuilder AddUserStore(this IIdentityServerBuilder builder, Action<DbContextOptionsBuilder> userStoreOptions = null)
        {
            builder.Services.AddDbContext<UserStoreDbContext>(userStoreOptions);
            builder.Services.AddTransient<UserStore>();
            return builder;
        }
    }

 在Startup.ConfigureServices方法中以下面代码替换AddTestUser(Config.GetUsers)

          //.AddTestUsers(Config.GetUsers())
                .AddUserStore(opt =>
                {
                    opt.UseMySql(connection, sql =>
                    {
                        sql.MigrationsAssembly("IdentityServer");
                    });
                })

添加用户存储数据表,在程序包管理器中输入以下指令

PM> add-migration UserStoreContext -c UserStoreDbContext -o Data/Migrations/IdentityServer/UserDb
PM> update-database -c UserStoreDbContext
Multiple startup projects set.
Using project 'srcIdentityServer' as the startup project.
Done.
PM> 

在数据库初始方法中添加Config类中的用户数据

 private void InitializeDatabase(IApplicationBuilder app)
        {
            using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
            {
                var context = serviceScope.ServiceProvider.GetRequiredService<ConfigurationDbContext>();
                var userContext = serviceScope.ServiceProvider.GetRequiredService<UserStoreDbContext>();
                //添加config中的客户端数据到数据库
                if (!context.Clients.Any())
                {
                    foreach (var client in Config.GetClients())
                    {
                        context.Clients.Add(client.ToEntity());
                    }
                    context.SaveChanges();
                }
                //添加config中的IdentityResources数据到数据库
                if (!context.IdentityResources.Any())
                {
                    foreach (var resource in Config.GetIdentityResources())
                    {
                        context.IdentityResources.Add(resource.ToEntity());
                    }
                    context.SaveChanges();
                }
                //添加config中的ApiResources数据到数据库
                if (!context.ApiResources.Any())
                {
                    foreach (var resource in Config.GetApis())
                    {
                        context.ApiResources.Add(resource.ToEntity());
                    }
                    context.SaveChanges();
                }
                //添加config中的Users数据到数据库
                if (!userContext.IdentityUser.Any())
                {
                    int index = 0;
                    foreach(var user in Config.GetUsers())
                    {
                        IdentityUser iuser = new IdentityUser()
                        {
                            IsActive = user.IsActive,
                            Password = user.Password,
                            ProviderName = user.ProviderName,
                            ProviderSubjectId = user.ProviderSubjectId,
                            SubjectId = user.SubjectId,
                            Username = user.Username,
                            IdentityUserClaims = user.Claims.Select(r => new IdentityUserClaim()
                            {
                                ClaimId = (index++).ToString(),
                                Name = r.Type,
                                Value = r.Value
                            }).ToList()
                        };
                        userContext.IdentityUser.Add(iuser);
                    }
                    userContext.SaveChanges();
                }
            }
        }

   可以看到数据库已经有用户数据了

 最后一步,登录时改用上面新建的UserStore类来验证登录密码以及查询用户信息,并添加用户Claim。

AccountController构造函数

 //private readonly TestUserStore _users; 这里改成了UserStore
        private readonly UserStore _users;
        private readonly IIdentityServerInteractionService _interaction;
        private readonly IClientStore _clientStore;
        private readonly IAuthenticationSchemeProvider _schemeProvider;
        private readonly IEventService _events;
        public AccountController(
            IIdentityServerInteractionService interaction,
            IClientStore clientStore,
            IAuthenticationSchemeProvider schemeProvider,
            IEventService events,
            //TestUserStore _users=null,这里改成了UserStore
            UserStore users)
        {
            // _users = User ?? new TestUserStore(Config.GetUsers()); 这里改成了UserStore
            _users = users;
            _interaction = interaction;
            _clientStore = clientStore;
            _schemeProvider = schemeProvider;
            _events = events;
        }

 Task<IActionResult> Login(LoginInputModel model, string button),登录时把数据库中的IdentityUserClaims数据转化为Claim数据传入登录重载方法。

  List<Claim> claims = user.IdentityUserClaims.Select(r => new Claim(r.Name,r.Value) ).ToList();
  await HttpContext.SignInAsync(user.SubjectId, user.Username, props, claims.ToArray());

  运行IdentityServer,IdentityApi,IdentityMvc三个项目后进行登录授权-获取access_token-访问api

  

  

  

  

 

 

  

  

  

 

原文地址:https://www.cnblogs.com/liujiabing/p/11599146.html