如何将自定义表添加到 configurationDbContext?
How can I add custom tables to configurationDbContext?
我希望自定义 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 有 2 个问题:
迁移构建不正确:
Unable to create an object of type 'IdSrvConfigurationDbContext'. Add an implementation of 'IDesignTimeDbContextFactory' to the project, or see https://go.microsoft.com/fwlink/?linkid=851728 for additional patterns supported at design time.
我无法从启动时正确访问扩展上下文。我不确定如何正确连接它。
我确信有一种正确的方法可以通过扩展身份选项构建器来连接它,但我不知道如何做到这一点。任何帮助将不胜感激。
无论您如何以及在何处存储该配置,它仍然仅在应用程序启动时使用。不幸的是,标准中间件的设计不利于运行时配置更改。
为了完成这项工作,我必须修改可以在运行时通过 Challenge 方法接受配置的 OIDC 中间件。您可能也必须为 SAML 中间件执行此操作。
我找到了这个问题的解决方案,我可以在其中动态地向 SchemeProvider 添加一个 Scheme。
参考:
这是控制器上的挑战方式,会增加一个scheme。
[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);
}
}
这是一个 class,它将在方案提供者中查找方案,如果不存在,它将尝试从数据库中添加。
/// <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;
}
}
我希望自定义 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 有 2 个问题:
迁移构建不正确:
Unable to create an object of type 'IdSrvConfigurationDbContext'. Add an implementation of 'IDesignTimeDbContextFactory' to the project, or see https://go.microsoft.com/fwlink/?linkid=851728 for additional patterns supported at design time.
我无法从启动时正确访问扩展上下文。我不确定如何正确连接它。
我确信有一种正确的方法可以通过扩展身份选项构建器来连接它,但我不知道如何做到这一点。任何帮助将不胜感激。
无论您如何以及在何处存储该配置,它仍然仅在应用程序启动时使用。不幸的是,标准中间件的设计不利于运行时配置更改。
为了完成这项工作,我必须修改可以在运行时通过 Challenge 方法接受配置的 OIDC 中间件。您可能也必须为 SAML 中间件执行此操作。
我找到了这个问题的解决方案,我可以在其中动态地向 SchemeProvider 添加一个 Scheme。
参考:
这是控制器上的挑战方式,会增加一个scheme。
[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);
}
}
这是一个 class,它将在方案提供者中查找方案,如果不存在,它将尝试从数据库中添加。
/// <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;
}
}