L'approche suggérée par McGuire fonctionnera avec OpenIddict (vous pouvez accéder au fichier acr_values
propriété par l'intermédiaire de OpenIdConnectRequest.AcrValues
) mais ce n'est pas l'option recommandée (ce n'est pas idéal du point de vue de la sécurité : comme l'émetteur est le même pour tous les locataires, ils finissent par partager les mêmes clés de signature).
En revanche, vous pouvez envisager de gérer un émetteur par locataire. Pour cela, vous avez au moins 2 options :
-
Donner Module OpenID d'OrchardCore un essai Il est basé sur OpenIddict et supporte nativement le multi-tenant. Il est encore en beta mais il est activement développé.
-
Remplacer le moniteur d'options utilisé par OpenIddict pour utiliser des options par locataire. .
Voici un exemple simplifié de la deuxième option, qui utilise un moniteur personnalisé et une résolution de locataire basée sur le chemin d'accès :
Mettez en œuvre la logique de résolution des problèmes des locataires. Par exemple
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();
}
Mettre en œuvre un IOptionsMonitor<OpenIddictServerOptions>
:
public class OpenIddictServerOptionsProvider : IOptionsMonitor<OpenIddictServerOptions>
{
private readonly ConcurrentDictionary<(string name, string tenant), Lazy<OpenIddictServerOptions>> _cache;
private readonly IOptionsFactory<OpenIddictServerOptions> _optionsFactory;
private readonly TenantProvider _tenantProvider;
public OpenIddictServerOptionsProvider(
IOptionsFactory<OpenIddictServerOptions> optionsFactory,
TenantProvider tenantProvider)
{
_cache = new ConcurrentDictionary<(string, string), Lazy<OpenIddictServerOptions>>();
_optionsFactory = optionsFactory;
_tenantProvider = tenantProvider;
}
public OpenIddictServerOptions CurrentValue => Get(Options.DefaultName);
public OpenIddictServerOptions Get(string name)
{
var tenant = _tenantProvider.GetCurrentTenant();
Lazy<OpenIddictServerOptions> Create() => new Lazy<OpenIddictServerOptions>(() => _optionsFactory.Create(name));
return _cache.GetOrAdd((name, tenant), _ => Create()).Value;
}
public IDisposable OnChange(Action<OpenIddictServerOptions, string> listener) => null;
}
Mettre en œuvre un IConfigureNamedOptions<OpenIddictServerOptions>
:
public class OpenIddictServerOptionsInitializer : IConfigureNamedOptions<OpenIddictServerOptions>
{
private readonly IDataProtectionProvider _dataProtectionProvider;
private readonly TenantProvider _tenantProvider;
public OpenIddictServerOptionsInitializer(
IDataProtectionProvider dataProtectionProvider,
TenantProvider tenantProvider)
{
_dataProtectionProvider = dataProtectionProvider;
_tenantProvider = tenantProvider;
}
public void Configure(string name, OpenIddictServerOptions options) => Configure(options);
public void Configure(OpenIddictServerOptions options)
{
var tenant = _tenantProvider.GetCurrentTenant();
// Create a tenant-specific data protection provider to ensure authorization codes,
// access tokens and refresh tokens can't be read/decrypted by the other tenants.
options.DataProtectionProvider = _dataProtectionProvider.CreateProtector(tenant);
// Other tenant-specific options can be registered here.
}
}
Enregistrez les services dans votre conteneur DI :
public void ConfigureServices(IServiceCollection services)
{
// ...
// Register the OpenIddict services.
services.AddOpenIddict()
.AddCore(options =>
{
// Register the Entity Framework stores.
options.UseEntityFrameworkCore()
.UseDbContext<ApplicationDbContext>();
})
.AddServer(options =>
{
// Register the ASP.NET Core MVC binder used by OpenIddict.
// Note: if you don't call this method, you won't be able to
// bind OpenIdConnectRequest or OpenIdConnectResponse parameters.
options.UseMvc();
// Note: the following options are registered globally and will be applicable
// to all the tenants. They can be overridden from OpenIddictServerOptionsInitializer.
options.AllowAuthorizationCodeFlow();
options.EnableAuthorizationEndpoint("/connect/authorize")
.EnableTokenEndpoint("/connect/token");
options.DisableHttpsRequirement();
});
services.AddSingleton<TenantProvider>();
services.AddSingleton<IOptionsMonitor<OpenIddictServerOptions>, OpenIddictServerOptionsProvider>();
services.AddSingleton<IConfigureOptions<OpenIddictServerOptions>, OpenIddictServerOptionsInitializer>();
}
Pour confirmer que cela fonctionne correctement, naviguez jusqu'à http://localhost :[port]/fabrikam/.well-known/openid-configuration (vous devriez obtenir une réponse JSON avec les métadonnées OpenID Connect).