zoukankan      html  css  js  c++  java
  • 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

      

      

      

      

     

     

      

      

      

     

  • 相关阅读:
    【题解】2020 年电子科技大学 ACMICPC 暑假前集训 数据结构
    【逆向】某触控板驱动分析过程
    SME 2019 ACM 题解
    数据结构 & 算法模板汇总
    VS2010win32下cocos2dx控制台打印的方法
    CDMA写码与鉴权(转载)
    mapxtreme开发小结2(c#)
    LONG GetWindowLong函数功能
    无边框的对话框的大小拖动实现
    YUV介绍
  • 原文地址:https://www.cnblogs.com/liujiabing/p/11599146.html
Copyright © 2011-2022 走看看