Fork me on GitHub

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

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

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

国际化

https://github.com/johnkors/IdentityServer3.Contrib.Localization

缓存

https://github.com/AliBazzi/IdentityServer3.Contrib.RedisStore

客户端

https://github.com/IdentityModel/IdentityModel2

配置信息持久化(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 关系OAuth项目结构

image

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 类

 public class Startup
    {
        /// <summary>
        /// 配置Idsv授权服务
        /// </summary>
        /// <param name="app"></param>
        public void Configuration(IAppBuilder app)
        {
            #region OAuth 2.0 服务端初始化
            //配置EF
            var ef = new EntityFrameworkServiceOptions
            {
                ConnectionString = DbSetting.OAuth2,
            };

            var factory = new IdentityServerServiceFactory();
            //注册Client与Scope的实现
            factory.RegisterConfigurationServices(ef);
            //注册Token实现
            factory.RegisterOperationalServices(ef);
            //自定义用户服务
            factory.UserService = new Registration<IUserService>(resolver => AutofacDependencyResolver.Current.RequestLifetimeScope.Resolve<IdSvrUserService>());
            //自定义视图
            factory.ViewService = new Registration<IViewService, IdSvrMvcViewService<LoginController>>();

            factory.Register(new Registration<HttpContext>(resolver => HttpContext.Current));
            factory.Register(new Registration<HttpContextBase>(resolver => new HttpContextWrapper(resolver.Resolve<HttpContext>())));
            //注册Request
            factory.Register(new Registration<HttpRequestBase>(resolver => resolver.Resolve<HttpContextBase>().Request));
            //注册Response
            factory.Register(new Registration<HttpResponseBase>(resolver => resolver.Resolve<HttpContextBase>().Response));
            factory.Register(new Registration<HttpServerUtilityBase>(resolver => resolver.Resolve<HttpContextBase>().Server));
            //注册Session
            factory.Register(new Registration<HttpSessionStateBase>(resolver => resolver.Resolve<HttpContextBase>().Session));

            /*
             //注册 Redis 服务
            factory.Register(new Registration<IDatabaseAsync>(resolver => ConnectionMultiplexer.Connect(CacheSetting.Redis).GetDatabase()));
            factory.AuthorizationCodeStore = new Registration<IAuthorizationCodeStore, IdentityServer3.Contrib.RedisStore.Stores.AuthorizationCodeStore>();
            factory.TokenHandleStore = new Registration<ITokenHandleStore, IdentityServer3.Contrib.RedisStore.Stores.TokenHandleStore>();
            factory.RefreshTokenStore = new Registration<IRefreshTokenStore, IdentityServer3.Contrib.RedisStore.Stores.RefreshTokenStore>();
           */

            /*
                //客户端信息缓存
                var clientStoreCache = new ClientStoreCache(redis);
                //作用域信息缓存
                var scopeStoreCache = new ScopeStoreCache(redis);
                //用户信息缓存
                var userServiceCache = new UserServiceCache(redis);
                //注册客户端缓存-
                factory.ConfigureClientStoreCache(new Registration<ICache<Client>>(clientStoreCache));
                //注册作用域缓存
                factory.ConfigureScopeStoreCache(new Registration<ICache<IEnumerable<Scope>>>(scopeStoreCache));
                //注册用户缓存
                // factory.ConfigureUserServiceCache(new Registration<ICache<IEnumerable<Claim>>>(userServiceCache));
                // factory.ConfigureUserServiceCache(TimeSpan.FromMilliseconds(1000 * 10));
             */

            //Idsv 配置
            app.UseIdentityServer(new IdentityServerOptions
            {
                SiteName = "Embedded Homeinns PMS 2.0 OAuth2 Service",
                EnableWelcomePage = true,
                Factory = factory,
                RequireSsl = Constants.RequireSsl,
                PublicOrigin = Constants.PublicOrigin,
                LoggingOptions = new LoggingOptions()
                {
                    EnableHttpLogging = true,
                    // EnableKatanaLogging = true,
                    // EnableWebApiDiagnostics = true,
                    // WebApiDiagnosticsIsVerbose = true,
                },
                SigningCertificate = new X509Certificate2(string.Format(@"{0}\IdSvr\idsrv3test.pfx", AppDomain.CurrentDomain.BaseDirectory), "idsrv3test"),
                EventsOptions = new EventsOptions
                {
                    RaiseSuccessEvents = true,
                    RaiseErrorEvents = true,
                    RaiseFailureEvents = true,
                    RaiseInformationEvents = true,
                },
                CspOptions = new CspOptions
                {
                    Enabled = false,
                },
                AuthenticationOptions = new AuthenticationOptions
                {
                    CookieOptions = new IdentityServer3.Core.Configuration.CookieOptions
                    {
                        SlidingExpiration = true,
                    },
                    EnablePostSignOutAutoRedirect = true,
                    EnableLocalLogin = true,
                    EnableSignOutPrompt = false
                }
            });
            //启动清除过期票据定时器
            var cleanToken = new TokenCleanup(ef, 20);
            cleanToken.Start();
            #endregion

            #region OAuth 2.0 管理后台 初始化
            /*
            //管理员功能 初始化
            app.Map("/admin", adminApp =>
            {
                var factoryAdmin = new IdentityAdmin.Configuration.IdentityAdminServiceFactory();
                //注入配置
                factoryAdmin.Configure();
                //注册管理员
                adminApp.UseIdentityAdmin(new IdentityAdmin.Configuration.IdentityAdminOptions
                {
                    Factory = factoryAdmin,
                    //AdminSecurityConfiguration =
                });
            });
             */
            #endregion 
        }
    }

 

 客户端模式问题

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

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

posted @ 2016-08-19 17:01  花儿笑弯了腰  阅读(6385)  评论(7编辑  收藏  举报