基于 IdentityServer3 实现 OAuth 2.0 授权服务数据持久化

最近花了一点时间,阅读了IdentityServer的源码,大致了解项目整体的抽象思维、面向对象的重要性; 生产环境如果要使用 IdentityServer3 ,主要涉及授权服务,资源服务的部署负载的问题,客户端(clients),作用域(scopes),票据(token)一定都要持久化, 客户端与作用域的持久化只需要实现 IClientStore 与 IScopeStore 的接口,可以自己实现,也可以直接使用 IdentityServer3 自身的扩展 IdentityServer3.EntityFramework

基于 IdentityServer3 实现 OAuth 2.0 授权服务数据持久化_第1张图片

Package

核心类库
Install-Package IdentityServer3
IdentityServer 核心库,只支持基于内存的客户端信息与用户信息配置

配置信息持久化
客户端,作用域,票据的持久化 ,支持的扩展有两个,一个基于 EF,另外一个使用MongoDb(社区支持)
Install-Package IdentityServer3.EntityFramework
Install-Package IdentityServer.v3.MongoDb

用户持久化

用户的持久化支持 MembershipReboot 与 ASP.NET Identity 两种
Install-Package IdentityServer3.MembershipReboot
Install-Package IdentityServer3.AspNetIdentity

其他插件

WS-Federation
Install-Package IdentityServer3.WsFederation
Access token validation middleware(验证中间件)
Install-Package IdentityServer3.AccessTokenValidation

配置信息持久化(Entity Framework support for Clients, Scopes, and Operational Data)

客户端(clients)与作用域(scopes)的持久化

客户端与作用域的持久化只需要实现 IClientStore 与 IScopeStore 的接口,默认EF 在 IdentityServerServiceFactory 实现了 RegisterClientStore 与 RegisterScopeStore 两个扩展方法,也可以使用 RegisterConfigurationServices 方法,默认包含以上两个扩展方法合集;RegisterOperationalServices 扩展方法实现 IAuthorizationCodeStore, ITokenHandleStore, IRefreshTokenStore, and IConsentStore 功能等。

可以在 IdentityServer3.EntityFramework 的项目中找到数据库的初始SQL ,

ER 关系基于 IdentityServer3 实现 OAuth 2.0 授权服务数据持久化_第2张图片项目结构

IdentityServerServiceFactoryExtensions 类扩展 IdentityServerServiceFactory 实现方法来持久化信息,最后 Registration 到接口上

public static class IdentityServerServiceFactoryExtensions
    {
        public static void RegisterOperationalServices(this IdentityServerServiceFactory factory, EntityFrameworkServiceOptions options)
        {
            if (factory == null) throw new ArgumentNullException("factory");
            if (options == null) throw new ArgumentNullException("options");

            factory.Register(new Registration<IOperationalDbContext>(resolver => new OperationalDbContext(options.ConnectionString, options.Schema)));
            factory.AuthorizationCodeStore = new Registration<IAuthorizationCodeStore, AuthorizationCodeStore>();
            factory.TokenHandleStore = new Registration<ITokenHandleStore, TokenHandleStore>();
            factory.ConsentStore = new Registration<IConsentStore, ConsentStore>();
            factory.RefreshTokenStore = new Registration<IRefreshTokenStore, RefreshTokenStore>();
        }

        public static void RegisterConfigurationServices(this IdentityServerServiceFactory factory, EntityFrameworkServiceOptions options)
        {
            factory.RegisterClientStore(options);
            factory.RegisterScopeStore(options);
        }

        public static void RegisterClientStore(this IdentityServerServiceFactory factory, EntityFrameworkServiceOptions options)
        {
            if (factory == null) throw new ArgumentNullException("factory");
            if (options == null) throw new ArgumentNullException("options");

            factory.Register(new Registration<IClientConfigurationDbContext>(resolver => new ClientConfigurationDbContext(options.ConnectionString, options.Schema)));
            factory.ClientStore = new Registration<IClientStore, ClientStore>();
            factory.CorsPolicyService = new ClientConfigurationCorsPolicyRegistration(options);
        }
        
        public static void RegisterScopeStore(this IdentityServerServiceFactory factory, EntityFrameworkServiceOptions options)
        {
            if (factory == null) throw new ArgumentNullException("factory");
            if (options == null) throw new ArgumentNullException("options");

            factory.Register(new Registration<IScopeConfigurationDbContext>(resolver => new ScopeConfigurationDbContext(options.ConnectionString, options.Schema)));
            factory.ScopeStore = new Registration<IScopeStore, ScopeStore>();
        }
    }

TokenCleanup 类负责定时清除过期的票据信息

public class TokenCleanup
    {
        private readonly static ILog Logger = LogProvider.GetCurrentClassLogger();

        EntityFrameworkServiceOptions options;
        CancellationTokenSource source;
        TimeSpan interval;

        public TokenCleanup(EntityFrameworkServiceOptions options, int interval = 60)
        {
            if (options == null) throw new ArgumentNullException("options");
            if (interval < 1) throw new ArgumentException("interval must be more than 1 second");

            this.options = options;
            this.interval = TimeSpan.FromSeconds(interval);
        }

        public void Start()
        {
            if (source != null) throw new InvalidOperationException("Already started. Call Stop first.");
            
            source = new CancellationTokenSource();
            Task.Factory.StartNew(()=>Start(source.Token));
        }
        
        public void Stop()
        {
            if (source == null) throw new InvalidOperationException("Not started. Call Start first.");

            source.Cancel();
            source = null;
        }

        public async Task Start(CancellationToken cancellationToken)
        {
            while (true)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    Logger.Info("CancellationRequested");
                    break;
                }

                try
                {
                    await Task.Delay(interval, cancellationToken);
                }
                catch
                {
                    Logger.Info("Task.Delay exception. exiting.");
                    break;
                }

                if (cancellationToken.IsCancellationRequested)
                {
                    Logger.Info("CancellationRequested");
                    break;
                }

                await ClearTokens();
            }
        }

        public virtual IOperationalDbContext CreateOperationalDbContext()
        {
            return new OperationalDbContext(options.ConnectionString, options.Schema);
        }

        private async Task ClearTokens()
        {
            try
            {
                Logger.Info("Clearing tokens");
                using (var db = CreateOperationalDbContext())
                {
                    var query =
                        from token in db.Tokens
                        where token.Expiry < DateTimeOffset.UtcNow
                        select token;

                    db.Tokens.RemoveRange(query);

                    await db.SaveChangesAsync();
                }
            }
            catch(Exception ex)
            {
                Logger.ErrorException("Exception cleaning tokens", ex);
            }
        }
    }

配置Idsv授权服务

Startup 类修改

/// <summary>
    /// OAuth2 服务配置
    /// </summary>
    public class Startup
    {
        /// <summary>
        /// 配置Idsv授权服务
        /// </summary>
        /// <param name="app"></param>
        public void Configuration(IAppBuilder app)
        {
            /*
            //改内存的数据持久化到DB
            Factory = new IdentityServerServiceFactory()
                          .UseInMemoryClients(Clients.Get())
                          .UseInMemoryScopes(Scopes.Get())
                          .UseInMemoryUsers(Users.Get()),
            */

            //EF配置
            var ef = new EntityFrameworkServiceOptions
            {
                ConnectionString = DbSetting.OAuth2,
            };
            var factory = new IdentityServerServiceFactory();
            //注册Client与Scope的实现
            factory.RegisterConfigurationServices(ef);
            //注册Token实现
            factory.RegisterOperationalServices(ef);
            //注册用户(基于用户表)
            factory.UseInMemoryUsers(new List<InMemoryUser>()
            {
                //测试用户
            });
            //配置缓存策略(默认5 min)
            factory.ConfigureClientStoreCache();
            factory.ConfigureScopeStoreCache();
            factory.ConfigureUserServiceCache(TimeSpan.FromMinutes(1));
            //idsv配置
            app.UseIdentityServer(new IdentityServerOptions
            {
                SiteName = "Embedded OAuth2 Service",
                EnableWelcomePage = true,
                Factory = factory,
                RequireSsl = false,
                //SigningCertificate = new X509Certificate2(string.Format(@"{0}\bin\identityServer\idsrv3test.pfx", AppDomain.CurrentDomain.BaseDirectory), "idsrv3test")
            });
            //启动清除过期票据定时器
            var cleanToken = new TokenCleanup(ef, 20);
            cleanToken.Start();
        }

客户端模式问题

  • 客户端,作用域,票据的持久化 [OK]
  • 限制客户端每天获得新票据的次数
  • 票据过期删除的策略 [OK]
  • 授权服务器客户端信息缓存策略 [OK]
  • 资源服务器票据验证的缓存策略 [OK]
  • 作用域权限范围控制
  • ClientId 与 ClientSecret 的生成规则 [OK]
  • 密码模式用户的身份验证 https://github.com/IdentityServer/IdentityServer3.AspNetIdentity

REFER:
Deployment
https://identityserver.github.io/Documentation/docsv2/advanced/deployment.html

你可能感兴趣的:(基于 IdentityServer3 实现 OAuth 2.0 授权服务数据持久化)