使用Dapper持久化IdentityServer4
最近研究dotnet core,微軟將IdentityServer4作為推薦的服務(wù)授權(quán)和驗證的組件,其獨立性特別適合微服務(wù)或者分布式的服務(wù)擴展驗證,所以非常受廣大dotnet開發(fā)人員的青睞.默認的IdentityServer4默認使用內(nèi)存對象的驗證和授權(quán),而在IdentityServer的官方推薦只有Entity Framework core的集成,默認也只有SQL Server的實例,如果想要使用MySQL等其他數(shù)據(jù)庫,Google了好多或多或少都遇到了很多坑,本人也嘗試了N(N>4)小時,最終還是放棄使用EF Core,改用比較透明化的Dapper來實現(xiàn)持久層.最終的項目地址如下:https://github.com/DarinHan/IdentityServer4.Dapper
關(guān)于該項目的使用方法參考項目的說明,有一份MySQL的Demo示例,如果使用SQL Server,也有類似的方法供使用.
下面就具體實現(xiàn)原理給大家介紹下細節(jié),方便大家理解IdentityServer4的內(nèi)部原理.
在研究了IdentityServer4.EntityFramework的源代碼后,知道要實現(xiàn)IdentityServer中內(nèi)置對象的Client,Apiresource,Identityresource,PersistedGrant的持久化,主要要實現(xiàn)3個Store,即項目中的ClientStore,ResourceStore,PersistedGrantStore.
從字面意思來看,這三個Store承擔(dān)著商店的角色,提供Client,Resource以及PersistedGrant的查詢等功能.此外我們知道如果需要查詢這三個對象,前提是我們得先保存到數(shù)據(jù)庫中,所以對于Client對象我們需要實現(xiàn)查詢(Store的角色),新增和修改的功能,這樣如果我們在管理后臺中才能通過新增和修改的功能動態(tài)維護這些對象.為了不污染Store在IdentityServer中的定義,我們引入IProvider接口的概念,分別對應(yīng)四個接口(區(qū)分API和Identity)如下
using IdentityServer4.Models;
using System.Collections.Generic;
?
namespace IdentityServer4.Dapper.Interfaces
{
? ? public interface IClientProvider
? ? {
? ? ? ? Client FindClientById(string clientid);
? ? ? ? void Add(Client client);
? ? ? ? IEnumerable<string> QueryAllowedCorsOrigins();??
? ? }
}
using IdentityServer4.Models;
using System.Collections.Generic;
?
namespace IdentityServer4.Dapper.Interfaces
{
? ? public interface IIdentityResourceProvider
? ? {
? ? ? ? IEnumerable<IdentityResource> FindIdentityResourcesByScope(IEnumerable<string> scopeNames);
? ? ? ? IEnumerable<IdentityResource> FindIdentityResourcesAll();
? ? ? ? void Add(IdentityResource identityResource);
? ? ? ? IdentityResource FindIdentityResourcesByName(string name);
? ? }
}
using IdentityServer4.Models;
using System.Collections.Generic;
?
namespace IdentityServer4.Dapper.Interfaces
{
? ? public interface IApiResourceProvider
? ? {
? ? ? ? ApiResource FindApiResource(string name);
? ? ? ? IEnumerable<ApiResource> FindApiResourcesByScope(IEnumerable<string> scopeNames);
? ? ? ? IEnumerable<ApiResource> FindApiResourcesAll();
? ? ? ? void Add(ApiResource apiResource);
? ? }
}
using IdentityServer4.Models;
using System.Collections.Generic;
?
namespace IdentityServer4.Dapper.Interfaces
{
? ? public interface IPersistedGrantProvider
? ? {
? ? ? ? IEnumerable<PersistedGrant> GetAll(string subjectId);
? ? ? ? IEnumerable<PersistedGrant> GetAll(string subjectId, string clientId);
? ? ? ? IEnumerable<PersistedGrant> GetAll(string subjectId, string clientId, string type);
? ? ? ? PersistedGrant Get(string key);
? ? ? ? void Add(PersistedGrant token);
? ? ? ? void Update(PersistedGrant token);
? ? ? ? void RemoveAll(string subjectId, string clientId);
? ? ? ? void RemoveAll(string subjectId, string clientId, string type);
? ? ? ? void Remove(string key);
? ? ? ? void Store(PersistedGrant grant);
? ? }
}
在我們得Store中通過的注入的方式使用接口對應(yīng)的服務(wù),并實現(xiàn)對應(yīng)IStore對應(yīng)的接口方法,比如ClientStore實現(xiàn)如下.
? ? public class ClientStore : IClientStore
? ? {
? ? ? ? private readonly IClientProvider _clientDB;
? ? ? ? private readonly ILogger<ClientStore> _logger;
?
? ? ? ? public ClientStore(IClientProvider client, ILogger<ClientStore> logger)
? ? ? ? {
? ? ? ? ? ? _clientDB = client ?? throw new ArgumentNullException(nameof(client));
? ? ? ? ? ? _logger = logger;
? ? ? ? }
?
? ? ? ? public Task<Client> FindClientByIdAsync(string clientId)
? ? ? ? {
? ? ? ? ? ? var client = _clientDB.FindClientById(clientId);
?
? ? ? ? ? ? _logger.LogDebug("{clientId} found in database: {clientIdFound}", clientId, client != null);
? ? ? ? ? ? return Task.FromResult<Client>(client);
? ? ? ? }
? ? }
在Identity這樣,最終對應(yīng)Client的讀寫操作都轉(zhuǎn)移到了IClientProvider中.
Server4.Dapper.DefaultProviders命名空間下,我們提供了Iprovider的默認實現(xiàn).實現(xiàn)方法使用了Dapper和AutoMapper實現(xiàn)了數(shù)據(jù)庫操作,這里就不一一舉例了.值得一說的是部分對象字段都是SQL的關(guān)鍵字,直接執(zhí)行SQL會報錯,我們使用了數(shù)據(jù)庫中的列名保護的方法,具體實現(xiàn)方法在各個數(shù)據(jù)庫實例項目中配置.比如在MySQL的實現(xiàn)中,我們配置保護字符為'`'.
public static class IdentityServerDapperExtensions
? ? {
? ? ? ? public static IIdentityServerBuilder AddMySQLProvider(this IIdentityServerBuilder builder, Action<DBProviderOptions> dbProviderOptionsAction = null)
? ? ? ? {
? ? ? ? ? ? //config mysql
? ? ? ? ? ? var options = new DBProviderOptions();
? ? ? ? ? ? options.DbProviderFactory = new MySqlClientFactory();
? ? ? ? ? ? //get last insert id for insert actions
? ? ? ? ? ? options.GetLastInsertID = "select last_insert_id();";?
? ? ? ? ? ? //config the ColumnName protect string, mysql using "`"
? ? ? ? ? ? options.ColumnProtect = new System.Collections.Generic.Dictionary<string, string>();
? ? ? ? ? ? options.ColumnProtect.Add("left", "`");
? ? ? ? ? ? options.ColumnProtect.Add("right", "`");
? ? ? ? ? ? //add singgleton
? ? ? ? ? ? builder.Services.AddSingleton(options);?
? ? ? ? ? ? dbProviderOptionsAction?.Invoke(options);
? ? ? ? ? ? return builder;
? ? ? ? }
? ? }
最終實現(xiàn)的IProvider使用擴展方法注入到容器中.
public static IIdentityServerBuilder AddConfigurationStore(this IIdentityServerBuilder builder, Action<ConfigurationStoreOptions> storeOptionsAction = null)
? ? ? ? {
? ? ? ? ? ? var options = new ConfigurationStoreOptions();
? ? ? ? ? ? storeOptionsAction?.Invoke(options);
? ? ? ? ? ? builder.Services.AddSingleton(options);
?
? ? ? ? ? ? builder.Services.AddTransient<Interfaces.IClientProvider, DefaultProviders.DefaultClientProvider>();
? ? ? ? ? ? builder.Services.AddTransient<Interfaces.IApiResourceProvider, DefaultProviders.DefaultApiResourceProvider>();
? ? ? ? ? ? builder.Services.AddTransient<Interfaces.IIdentityResourceProvider, DefaultProviders.DefaultIdentityResourceProvider>();
?
? ? ? ? ? ? builder.AddClientStore<ClientStore>();
? ? ? ? ? ? builder.AddResourceStore<ResourceStore>();
? ? ? ? ? ? builder.AddCorsPolicyService<CorsPolicyService>();
? ? ? ? ? ? return builder;
? ? ? ? }
在OperationStore方法中,需要將原來IdentityServer4中默認提供的InMemory的實例移除,再添加新的實例.
? ? ? ? public static IIdentityServerBuilder AddOperationalStore(this IIdentityServerBuilder builder, Action<OperationalStoreOptions> storeOptionsAction = null)
? ? ? ? {
? ? ? ? ? ? builder.Services.AddSingleton<TokenCleanup>();
? ? ? ? ? ? builder.Services.AddSingleton<IHostedService, TokenCleanupHost>();//auto clear expired tokens
?
? ? ? ? ? ? builder.Services.AddTransient<Interfaces.IPersistedGrantProvider, DefaultProviders.DefaultPersistedGrantProvider>();
? ? ? ? ? ? builder.Services.AddTransient<Interfaces.IPersistedGrantStoreClanup, DefaultProviders.DefaultPersistedGrantProvider>();
?
? ? ? ? ? ? var storeOptions = new OperationalStoreOptions();
? ? ? ? ? ? storeOptionsAction?.Invoke(storeOptions);
? ? ? ? ? ? builder.Services.AddSingleton(storeOptions);
?
? ? ? ? ? ? var memopersistedstore = builder.Services.FirstOrDefault(c => c.ServiceType == typeof(IPersistedGrantStore));
? ? ? ? ? ? if (memopersistedstore != null)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? builder.Services.Remove(memopersistedstore);
? ? ? ? ? ? }
? ? ? ? ? ? builder.Services.AddSingleton<IPersistedGrantStore, PersistedGrantStore>();
? ? ? ? ? ? memopersistedstore = builder.Services.FirstOrDefault(c => c.ServiceType == typeof(IPersistedGrantStore));
? ? ? ? ? ? return builder;
? ? ? ? }
到此,基本的持久化改造已經(jīng)完成了,當(dāng)然在該項目中還實現(xiàn)了一個自動刪除過期Token的服務(wù),這個服務(wù)也是EFCore中實現(xiàn)的,基本上是把功能復(fù)制過來,具體細節(jié)稍有改造.
?
原文地址:https://blog.csdn.net/u013710468/article/details/81675747
.NET社區(qū)新聞,深度好文,歡迎訪問公眾號文章匯總 http://www.csharpkit.com
總結(jié)
以上是生活随笔為你收集整理的使用Dapper持久化IdentityServer4的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 人工智能第二课:认知服务和机器人框架探秘
- 下一篇: 再不学习我们就out了