IdentityServer4結合Mysql

 

上一篇:IdentityServer4使用OpenIdConnect實現單點登陸html

前面寫的示例中,IdeneityServer使用的是內存緩存的存儲方式,全部的配置都寫在Config.cs裏。在實際應用中,應該使用數據庫存儲方式,方便隨時配置,如添加新的用戶、資源、客戶端,也能夠節省服務器內存。mysql

本文從三個方面來實現IdentityServer4結合Mysql實現數據庫存儲方式,分別是客戶端及資源數據、令牌及受權碼數據以及用戶數據。sql

一,準備內容

1,準備MySql數據庫服務器,新建一個空的數據庫數據庫

2,IdentityServer須要安裝如下幾個程序包。json

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

3,appsettings.json添加數據庫鏈接字符串api

{
  "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,在控制檯輸入如下指令app

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更新數據庫到最新狀態。ide

 

 

 可是如今數據庫中的客戶端和資源數據都是爲空的,須要把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 'src\IdentityServer' 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

相關文章
相關標籤/搜索