Как я могу добавить пользовательские таблицы в конфигурацию DBContext?
Я ищу настроить IdentityServer4 для загрузки внешних поставщиков удостоверений из базы данных. Я хотел бы расширить ConfigurationDBContext, чтобы включить DbSet Saml2Provider. В моем стартапе я бы хотел автоматически добавить 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, если это возможно, так как это имеет смысл быть там.
Есть 2 вопроса, расширяющих ConfigurationDbContext:
Миграции строятся неправильно:
Невозможно создать объект типа 'IdSrvConfigurationDbContext'. Добавьте в проект реализацию IDesignTimeDbContextFactory или обратитесь к https://go.microsoft.com/fwlink/?linkid=851728 за дополнительными шаблонами, поддерживаемыми во время разработки.
Я не могу получить доступ к расширенному контексту при запуске правильно. Я не уверен, как правильно подключить его.
Я уверен, что есть правильный способ связать это путем расширения компоновщика опций Identity, но я не знаю, как это сделать. Любая помощь будет принята с благодарностью.
1 ответ
Я нашел решение этой проблемы, где я мог бы динамически добавить схему в SchemeProvider.
Ссылка: Динамически добавьте провайдера аутентификации SAML2, используя Sustainsys.Saml2 в 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);
}
}
и это класс, который будет искать схему в scheprovider, и, если она не существует, она попытается добавить ее из БД.
/// <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;
}
}
Независимо от того, как и где вы храните эту конфигурацию, она все еще используется только при запуске приложения. Конструкция стандартного промежуточного программного обеспечения, к сожалению, не способствует изменениям конфигурации во время выполнения.
Чтобы это работало, мне пришлось изменить промежуточное программное обеспечение OIDC, которое могло принимать конфигурацию во время выполнения с помощью метода Challenge. Возможно, вам придется сделать это и для промежуточного программного обеспечения SAML.