使用Dapper持久化IdentityServer4

最近研究dotnet core,微软将IdentityServer4作为推荐的服务授权和验证的组件,其独立性特别适合微服务或者分布式的服务扩展验证,所以非常受广大dotnet开发人员的青睐.默认的IdentityServer4默认使用内存对象的验证和授权,而在IdentityServer的官方推荐只有Entity Framework core的集成,默认也只有SQL Server的实例,如果想要使用MySQL等其他数据库,Google了好多或多或少都遇到了很多坑,本人也尝试了N(N>4)小时,最终还是放弃使用EF Core,改用比较透明化的Dapper来实现持久层.最终的项目地址如下:https://github.com/DarinHan/IdentityServer4.Dapper


关于该项目的使用方法参考项目的说明,有一份MySQL的Demo示例,如果使用SQL Server,也有类似的方法供使用.


下面就具体实现原理给大家介绍下细节,方便大家理解IdentityServer4的内部原理.


在研究了IdentityServer4.EntityFramework的源代码后,知道要实现IdentityServer中内置对象的Client,Apiresource,Identityresource,PersistedGrant的持久化,主要要实现3个Store,即项目中的ClientStore,ResourceStore,PersistedGrantStore.


从字面意思来看,这三个Store承担着商店的角色,提供Client,Resource以及PersistedGrant的查询等功能.此外我们知道如果需要查询这三个对象,前提是我们得先保存到数据库中,所以对于Client对象我们需要实现查询(Store的角色),新增和修改的功能,这样如果我们在管理后台中才能通过新增和修改的功能动态维护这些对象.为了不污染Store在IdentityServer中的定义,我们引入IProvider接口的概念,分别对应四个接口(区分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中通过的注入的方式使用接口对应的服务,并实现对应IStore对应的接口方法,比如ClientStore实现如下.


    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这样,最终对应Client的读写操作都转移到了IClientProvider中.


Server4.Dapper.DefaultProviders命名空间下,我们提供了Iprovider的默认实现.实现方法使用了Dapper和AutoMapper实现了数据库操作,这里就不一一举例了.值得一说的是部分对象字段都是SQL的关键字,直接执行SQL会报错,我们使用了数据库中的列名保护的方法,具体实现方法在各个数据库实例项目中配置.比如在MySQL的实现中,我们配置保护字符为'`'.


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;

        }

    }

最终实现的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;

        }

到此,基本的持久化改造已经完成了,当然在该项目中还实现了一个自动删除过期Token的服务,这个服务也是EFCore中实现的,基本上是把功能复制过来,具体细节稍有改造.

 

原文地址:https://blog.csdn.net/u013710468/article/details/81675747

 
 

.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

640?wx_fmt=jpeg

apper是一个轻量级的ORM框架,它可以帮助我们更方便地操作数据库。下面是使用Dapper进行数据库操作的示例代码: 首先,我们需要安装Dapper包。可以通过NuGet包管理器或者在项目中添加引用来完成安装。 ```csharp using System.Data.SqlClient; using Dapper; public class DapperHelper<T> { private volatile static DapperHelper<T> _instance = null; private static readonly object lockHelper = new object(); private DapperHelper() { } public static DapperHelper<T> Ins() { if (_instance == null) { lock (lockHelper) { if (_instance == null) { _instance = new DapperHelper<T>(); } } } return _instance; } private string connectionString = "Data Source=.;Initial Catalog=Test;Integrated Security=True"; public List<T> ExecutePro(string proc, object param) { using (SqlConnection con = new SqlConnection(connectionString)) { List<T> list = con.Query<T>( proc, param, null, true, null, CommandType.StoredProcedure).ToList(); return list; } } } ``` 上面的代码中,我们定义了一个DapperHelper类,用于封装Dapper的操作。其中,ExecutePro方法用于执行存储过程,并返回结果集。 使用Dapper进行数据库操作的步骤如下: 1.创建SqlConnection对象,指定连接字符串。 2.调用Query方法执行SQL语句或存储过程,并将结果集转换为List<T>类型。 下面是一个使用DapperHelper类执行存储过程的示例代码: ```csharp var helper = DapperHelper<MyModel>.Ins(); var list = helper.ExecutePro("GetMyModelList", new { Id = 1 }); ``` 上面的代码中,我们首先获取DapperHelper实例,然后调用ExecutePro方法执行存储过程,并将结果集转换为List<MyModel>类型。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值