How can I set the Authority on OpenIdConnect middleware options dynamically?

ぐ巨炮叔叔 提交于 2019-12-05 00:59:14

问题


We have multiple tenants, and they use different authorities (their own, not just standard providers). While I know how to dynamically set the clientId and secret, I can't figure out how to set the authority. It is set once, during startup, and afterwards it cannot be changed (or so it seems).

Since we have a lot of tenants we don't want to register all at startup, and we also don't want to require a restart when tenants are added.

Any suggestions how I can go about this? I'd love to use the existing middleware, but if it's not possible I could write my own.

Appreciate any suggestion!


回答1:


While a bit tricky, it's definitely possible. Here's a simplified example, using the MSFT OIDC handler, a custom monitor and path-based tenant resolution:

Implement your tenant resolution logic. E.g:

public class TenantProvider
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public TenantProvider(IHttpContextAccessor httpContextAccessor)
        => _httpContextAccessor = httpContextAccessor;

    public string GetCurrentTenant()
    {
        // This sample uses the path base as the tenant.
        // You can replace that by your own logic.
        string tenant = _httpContextAccessor.HttpContext.Request.PathBase;
        if (string.IsNullOrEmpty(tenant))
        {
            tenant = "default";
        }

        return tenant;
    }
}
public void Configure(IApplicationBuilder app)
{
    app.Use(next => context =>
    {
        // This snippet uses a hardcoded resolution logic.
        // In a real world app, you'd want to customize that.
        if (context.Request.Path.StartsWithSegments("/fabrikam", out PathString path))
        {
            context.Request.PathBase = "/fabrikam";
            context.Request.Path = path;
        }

        return next(context);
    });

    app.UseAuthentication();

    app.UseMvc();
}

Implement a custom IOptionsMonitor<OpenIdConnectOptions>:

public class OpenIdConnectOptionsProvider : IOptionsMonitor<OpenIdConnectOptions>
{
    private readonly ConcurrentDictionary<(string name, string tenant), Lazy<OpenIdConnectOptions>> _cache;
    private readonly IOptionsFactory<OpenIdConnectOptions> _optionsFactory;
    private readonly TenantProvider _tenantProvider;

    public OpenIdConnectOptionsProvider(
        IOptionsFactory<OpenIdConnectOptions> optionsFactory,
        TenantProvider tenantProvider)
    {
        _cache = new ConcurrentDictionary<(string, string), Lazy<OpenIdConnectOptions>>();
        _optionsFactory = optionsFactory;
        _tenantProvider = tenantProvider;
    }

    public OpenIdConnectOptions CurrentValue => Get(Options.DefaultName);

    public OpenIdConnectOptions Get(string name)
    {
        var tenant = _tenantProvider.GetCurrentTenant();

        Lazy<OpenIdConnectOptions> Create() => new Lazy<OpenIdConnectOptions>(() => _optionsFactory.Create(name));
        return _cache.GetOrAdd((name, tenant), _ => Create()).Value;
    }

    public IDisposable OnChange(Action<OpenIdConnectOptions, string> listener) => null;
}

Implement a custom IConfigureNamedOptions<OpenIdConnectOptions>:

public class OpenIdConnectOptionsInitializer : IConfigureNamedOptions<OpenIdConnectOptions>
{
    private readonly IDataProtectionProvider _dataProtectionProvider;
    private readonly TenantProvider _tenantProvider;

    public OpenIdConnectOptionsInitializer(
        IDataProtectionProvider dataProtectionProvider,
        TenantProvider tenantProvider)
    {
        _dataProtectionProvider = dataProtectionProvider;
        _tenantProvider = tenantProvider;
    }

    public void Configure(string name, OpenIdConnectOptions options)
    {
        if (!string.Equals(name, OpenIdConnectDefaults.AuthenticationScheme, StringComparison.Ordinal))
        {
            return;
        }

        var tenant = _tenantProvider.GetCurrentTenant();

        // Create a tenant-specific data protection provider to ensure
        // encrypted states can't be read/decrypted by the other tenants.
        options.DataProtectionProvider = _dataProtectionProvider.CreateProtector(tenant);

        // Other tenant-specific options like options.Authority can be registered here.
    }

    public void Configure(OpenIdConnectOptions options)
        => Debug.Fail("This infrastructure method shouldn't be called.");
}

Register the services in your DI container:

public void ConfigureServices(IServiceCollection services)
{
    // ...

    // Register the OpenID Connect handler.
    services.AddAuthentication()
        .AddOpenIdConnect();

    services.AddSingleton<TenantProvider>();
    services.AddSingleton<IOptionsMonitor<OpenIdConnectOptions>, OpenIdConnectOptionsProvider>();
    services.AddSingleton<IConfigureOptions<OpenIdConnectOptions>, OpenIdConnectOptionsInitializer>();
}



回答2:


The Asp.NET Core model assumes one upstream authority per handler instance. My Saml2 component supports multiple upstream Idps in one handler and it has drawbacks in the rest of the system when that assumption no longer is true.

In Asp.NET Core it is possible to add/remove providers at runtime, without requiring a restart. So I'd recommend finding a model based on that.

If you rather want one handler that can have a per-request Authority setting, I think that a custom handler is needed - Microsoft's default implementation won't support that.



来源:https://stackoverflow.com/questions/52955238/how-can-i-set-the-authority-on-openidconnect-middleware-options-dynamically

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!