上一篇: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