基于 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 ,
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