在运行时替换 Cookie 身份验证处理程序方案选项

Replace Cookie Authentication Handler Scheme Options at Runtime

如果我使用本机依赖注入容器将 cookie 身份验证添加到我的 ASP.Net 核心应用程序,并使用 cookie 身份验证选项。那么如何在启动后 运行 时间替换身份验证选项?例如,如果我想在应用 运行ning 时更改 cookie 过期时间。我不知道如何用它的选项替换身份验证处理程序以影响更改。

启动时添加身份验证的代码:

public static IServiceCollection ConfigureOAuth(this IServiceCollection services)
{
    var appSettings = services.BuildServiceProvider().GetService<IOptions<AppSettings>>();

    return services.AddAuthentication(o =>
    {
        o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        o.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, (o) =>
    {
        o.ExpireTimeSpan = TimeSpan.FromHours(appSettings.Value.HostOptions.SessionLifespanHours);
    })
    .Services;
}

在 运行 时替换身份验证的代码:

/// <summary>
/// Replace authentication options with new ones read from configuration. 
/// 1). Remove old services
/// 2.) Reload the configuration 
/// 3.) Add the authentication scheme with options read from the latest configuration
/// </summary>
private static void ReplaceServices(IServiceCollection services, IHostingEnvironment env)
{
    ClearServices(services);

    services.Configure<AppSettings>(StartupConfiguration.BuildConfigurationRoot(env).GetSection("App"));

    var provider = services.BuildServiceProvider();
    var appSettings = provider.GetService<IOptions<AppSettings>>();

    services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<CookieAuthenticationOptions>, PostConfigureCookieAuthenticationOptions>());
    services.AddScheme<CookieAuthenticationOptions, CookieAuthenticationHandler>(CookieAuthenticationDefaults.AuthenticationScheme, (o) =>
    {
        o.ExpireTimeSpan = TimeSpan.FromHours(appSettings.Value.HostOptions.SessionLifespanHours);
    });
}

/// <summary>
/// Clear stale dependencies: application settings configured from appsettings.json, 
/// authentication options and cookie authentication handler and options
/// </summary>
private static void ClearServices(IServiceCollection services)
{
    var staleTypes = new List<Type>
    {
        typeof(IConfigureOptions<AppSettings>),
        typeof(IConfigureOptions<AuthenticationOptions>),
        typeof(IPostConfigureOptions<CookieAuthenticationOptions>),
        typeof(IConfigureOptions<CookieAuthenticationOptions>),
        typeof(CookieAuthenticationHandler)
    };

    foreach (var staleType in staleTypes)
    {
        var staleService = services.FirstOrDefault(s => s.ServiceType.Equals(staleType));
        services.Remove(staleService);
    }
}

Asp.net 核心本机配置重新加载可能有点不稳定。如果服务依赖于在 运行 时更改的应用程序设置,则您不必在启动时将这些设置作为 IOptions 注入。另一种方法是编写您的设置提供程序,当从文件系统观察程序收到事件通知时,它会重新加载设置的缓存副本。这种方法消除了将配置作为 DI 服务的需要,并且您不再需要依赖于重新加载令牌。流程如下:

  1. 创建封装读取和存储应用程序设置的配置提供程序服务。在初始化时,它读取 appsettings.json 并缓存 IConfigurationRoot
  2. 的实例
  3. 创建另一个服务来封装监视文件系统以更改应用程序设置。如果更改,请使用带有 FileChangeEvent 的简单 pub/sub 模式通知配置提供程序。配置提供者可以更新配置根目录,然后在刷新配置后触发 ConfigChangeEvent。
  4. 它们依赖于实时配置的任何服务,例如身份验证选项,都可以订阅 ConfigChangeEvent 并根据配置根目录中需要的部分更新其设置。

此处的关键是创建身份验证处理程序可以成功使用并始终具有实时值的选项服务。为此,您需要实施 IOptionsMonitor。

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(o =>
    {
        o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        o.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddSingleton<IOptionsMonitor<CookieAuthenticationOptions>, CookieAuthenticationConfigurator>()
    .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);
}

IOptionsMonitor 实现:

{
    internal class CookieAuthenticationConfigurator : IOptionsMonitor<CookieAuthenticationOptions>
    {
        private readonly FileConfigurationBuilder ConfigProvider;
        private readonly IHostingEnvironment Environment;
        private readonly IDataProtectionProvider DataProtectionProvider;
        private readonly IMessageHub Hub;

        public CookieAuthenticationConfigurator(FileConfigurationBuilder configProvider, IDataProtectionProvider dataProtectionProvider, IMessageHub hub, IHostingEnvironment environment)
        {
            ConfigProvider = configProvider;
            Environment = environment;
            DataProtectionProvider = dataProtectionProvider;
            Hub = hub;
            Initialize();
        }

        private void Initialize()
        {
            Hub.Subscribe<ConfigurationChangeEvent>(_ =>
            {
                Build();
            });

            Build();
        }

        private void Build()
        {
            var hostOptions = ConfigProvider.Get<HostOptions>("HostOptions");
            options = new CookieAuthenticationOptions
            {
                ExpireTimeSpan = TimeSpan.FromHours(hostOptions.SessionLifespanHours)
            };
        }

        private CookieAuthenticationOptions options;

        public CookieAuthenticationOptions CurrentValue => options;

        public CookieAuthenticationOptions Get(string name)
        {
            PostConfigureCookieAuthenticationOptions op = new PostConfigureCookieAuthenticationOptions(DataProtectionProvider);
            op.PostConfigure(name, options);
            return options;
        }

        public IDisposable OnChange(Action<CookieAuthenticationOptions, string> listener)
        {
            throw new NotImplementedException();
        }
    }
}