如何将自定义表添加到configurationDbContext?

时间:2019-02-06 23:21:53

标签: identityserver4 ef-migrations sustainsys-saml2

我正在寻找自定义IdentityServer4以从数据库加载外部身份提供程序的方法。我想扩展ConfigurationDBContext以包括Saml2Provider的DbSet。在启动时,我想自动添加Saml2Provider。理想情况下,我希望有一种简便的方法可以在idsvr4登录页面中刷新可用的提供程序列表,而不必重新启动应用程序。

我已经能够从数据库加载Saml2Providers并将其注册为外部提供程序。但是,这使用的是ApplicationDbcontext,并且不会在对idsvr的每个请求中刷新。

这是我正在运行的configureServices(使用ApplicationDbContext从数据库中检索提供程序):

public void ConfigureServices(IServiceCollection services)

    services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(connectionString));
    var builder = services.AddIdentityServer(options =>
        {
            options.Events.RaiseErrorEvents = true;
            options.Events.RaiseInformationEvents = true;
            options.Events.RaiseFailureEvents = true;
            options.Events.RaiseSuccessEvents = true;
        })
            // this adds the config data from DB (clients, resources)
            .AddConfigurationStore(options =>
            {

                options.Client.Schema = "config";
                options.DefaultSchema = "config";
                options.ConfigureDbContext = b =>
                    b.UseSqlServer(connectionString,
                        sql =>        sql.MigrationsAssembly(migrationsAssembly));
            })
            // this adds the operational data from DB (codes, tokens, consents)
            .AddOperationalStore(options =>
            {
                options.ConfigureDbContext = b => 
                    b.UseSqlServer(connectionString,
                        sql => sql.MigrationsAssembly(migrationsAssembly));

                // this enables automatic token cleanup. this is optional.
                options.EnableTokenCleanup = true;
            })
            .AddAspNetIdentity<ApplicationUser>()
            .AddProfileService<CustomProfileService>();
......
    var context = serviceProvider.GetService<ApplicationDbContext>();

    var saml2Providers = context.Saml2Providers.ToList();
     foreach(var provider in saml2Providers)
     {
        provider.RegisterService(services);
     }
}

这是我扩展ConfigurationDbContext的尝试:

public class IdSrvConfigurationDbContext : ConfigurationDbContext<IdSrvConfigurationDbContext>
{
    public DbSet<Saml2Provider> Saml2Providers { get; set; }

    public IdSrvConfigurationDbContext(DbContextOptions<IdSrvConfigurationDbContext> options, ConfigurationStoreOptions storeOptions) 
        :base(options, storeOptions)            
    {

    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        //mylogic here  
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<Saml2Provider>().ToTable("Saml2ProviderConfigContext", schema: "config");
    }
}

我希望在数据库中更新外部提供程序时自动在登录屏幕中刷新它们。我还想尽可能通过ConfigurationDbContext加载外部提供程序信息,因为在那里很有意义。

扩展ConfigurationDbContext有两个问题:

  1. 迁移无法正确构建:

      

    无法创建类型为“ IdSrvConfigurationDbContext”的对象。在项目中添加“ IDesignTimeDbContextFactory”的实现,或在设计时支持https://go.microsoft.com/fwlink/?linkid=851728的其他模式。

  2. 我无法从启动正确访问扩展上下文。我不确定如何正确连接它。

我敢肯定有一种通过扩展“身份”选项构建器进行连接的正确方法,但是我不知道如何执行此操作。任何帮助将不胜感激。

2 个答案:

答案 0 :(得分:0)

无论您以何种方式和位置存储该配置,它仍仅在应用程序启动时使用。不幸的是,标准中间件的设计不利于运行时配置的更改。

要完成这项工作,我必须修改OIDC中间件,该中间件可以在运行时通过Challenge方法接受配置。您可能也必须对SAML中间件执行此操作。

答案 1 :(得分:0)

我找到了解决此问题的方法,可以在其中动态地将Scheme添加到SchemeProvider。

参考:Dynamically add a SAML2 authentication provider using Sustainsys.Saml2 in ASP.NET Core

https://github.com/aspnet/AuthSamples/blob/master/samples/DynamicSchemes/Controllers/AuthController.cs

这是控制器上的质询方法,它将添加一个方案。

[HttpGet]
    public async Task<IActionResult> Challenge(string provider, string returnUrl)
    {
        if (string.IsNullOrEmpty(returnUrl))
        {
            returnUrl = "~/";
        }

        // validate returnUrl - either it is a valid OIDC URL or back to a local page
        if (Url.IsLocalUrl(returnUrl) == false && interaction.IsValidReturnUrl(returnUrl) == false)
        {
            // user might have clicked on a malicious link - should be logged
            throw new Exception("invalid return URL");
        }

        if (provider == AccountOptions.WindowsAuthenticationSchemeName)
        {
            // windows authentication needs special handling
            return await ProcessWindowsLoginAsync(returnUrl);
        }
        else
        {
            // start challenge and roundtrip the return URL and scheme
            var props = new AuthenticationProperties
            {
                RedirectUri = Url.Action(nameof(Callback)),
                Items =
                {
                    { "returnUrl", returnUrl },
                    { "scheme", provider },
                },
            };

            // Checks to see if the scheme exists in the provider, then will add a new one if it's found in the database.
            await schemeProviderLoader.TryAddScheme(provider);
            return Challenge(props, provider);
        }
    }

这是一个将在schemeprovider中查找方案的类,如果不存在,它将尝试从数据库中添加它。

/// <summary>
/// Helper class to dynamically add Saml2 Providers the SchemeProvider.
/// If the scheme is not found on the scheme provider it will look it up in the database and if found, will add it to the schemeprovider.
/// </summary>
public class SchemeProviderLoader
{
    private readonly ApplicationDbContext dbContext;
    private readonly IAuthenticationSchemeProvider schemeProvider;
    private readonly IOptionsMonitorCache<Saml2Options> optionsCache;
    private readonly ILogger logger;

    /// <summary>
    /// Initializes a new instance of the <see cref="SchemeProviderLoader"/> class.
    /// </summary>
    /// <param name="dbContext">Database context used to lookup providers.</param>
    /// <param name="schemeProvider">SchemeProvider to add the scheme to.</param>
    /// <param name="optionsCache">Options cache to add the scheme options to.</param>
    /// <param name="logger">Logger.</param>
    public SchemeProviderLoader(ApplicationDbContext dbContext, IAuthenticationSchemeProvider schemeProvider, IOptionsMonitorCache<Saml2Options> optionsCache, ILogger<SchemeProviderLoader> logger)
    {
        this.dbContext = dbContext;
        this.schemeProvider = schemeProvider;
        this.optionsCache = optionsCache;
        this.logger = logger;
    }

    /// <summary>
    /// Will dynamically add a scheme after startup.
    /// If the scheme is not found on the scheme provider it will look it up in the database and if found, will add it to the schemeprovider.
    /// </summary>
    /// <param name="scheme">The name of the identity provider to add.</param>
    /// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation. True if the scheme was found or added. False if it was not found.</returns>
    public async Task<bool> TryAddScheme(string scheme)
    {
        if (await schemeProvider.GetSchemeAsync(scheme) == null)
        {
            // Lookup to see if the scheme has been added to the saml2providers since the app was last loaded.
            var saml2Provider = await dbContext.Saml2Providers.FindAsync(scheme);
            if (saml2Provider == null)
            {
                return false;
            }

            // Add the scheme.
            schemeProvider.AddScheme(new AuthenticationScheme(scheme, saml2Provider.IdpCaption, typeof(Saml2Handler)));

            // Add saml2 options to the options cache
            Saml2Options options = new Saml2Options();
            saml2Provider.GetSaml2Options(options);
            options.SPOptions.Logger = new AspNetCoreLoggerAdapter(logger);
            optionsCache.TryAdd(scheme, options);
        }

        return true;
    }
}