我正在寻找自定义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有两个问题:
迁移无法正确构建:
无法创建类型为“ IdSrvConfigurationDbContext”的对象。在项目中添加“ IDesignTimeDbContextFactory”的实现,或在设计时支持https://go.microsoft.com/fwlink/?linkid=851728的其他模式。
我无法从启动正确访问扩展上下文。我不确定如何正确连接它。
我敢肯定有一种通过扩展“身份”选项构建器进行连接的正确方法,但是我不知道如何执行此操作。任何帮助将不胜感激。
答案 0 :(得分:0)
无论您以何种方式和位置存储该配置,它仍仅在应用程序启动时使用。不幸的是,标准中间件的设计不利于运行时配置的更改。
要完成这项工作,我必须修改OIDC中间件,该中间件可以在运行时通过Challenge方法接受配置。您可能也必须对SAML中间件执行此操作。
答案 1 :(得分:0)
我找到了解决此问题的方法,可以在其中动态地将Scheme添加到SchemeProvider。
参考:Dynamically add a SAML2 authentication provider using Sustainsys.Saml2 in ASP.NET Core
这是控制器上的质询方法,它将添加一个方案。
[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;
}
}