Asp.net-Core
openid connect - 在登錄期間辨識租戶
我有一個多租戶(單數據庫)應用程序,它允許跨不同租戶使用相同的使用者名/電子郵件。
登錄時(隱式流)如何辨識租戶?我想到了以下幾種可能:
- 在註冊時向使用者詢問帳戶
slug(公司/租戶 slug),在登錄時使用者應slug提供username和password。但是open id請求中沒有參數來發送slug。 2. 在註冊時創建一個
OAuth應用程序並slug用作client_id. 在登錄slug時client_id,我將使用它來獲取租戶 ID 並進一步驗證使用者。這種方法好嗎?
編輯:
還嘗試使 slug 成為路線參數的一部分
.EnableTokenEndpoint("/connect/{slug}/token");但 openiddict 不支持。
McGuire 建議的方法將適用於 OpenIddict(您可以通過 訪問該
acr_values屬性OpenIdConnectRequest.AcrValues),但這不是推薦的選項(從安全形度來看,這並不理想:由於所有租戶的發行人都是相同的,他們最終共享相同的簽名鍵)。相反,請考慮為每個租戶執行一個發行者。為此,您至少有 2 個選項:
- 試試OrchardCore的 OpenID 模組:它基於 OpenIddict 並且原生支持多租戶。它仍處於測試階段,但正在積極開發中。
- 覆蓋 OpenIddict 使用的選項監視器以使用 per-tenant options。
這是第二個選項的簡化範例,使用自定義監視器和基於路徑的租戶解析:
實現您的租戶解析邏輯。例如:
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(); }實現自定義
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; }實現自定義
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. } }在 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>(); }要確認這是否正常工作,請導航到http://localhost:$$ port $$/fabrikam/.well-known/openid-configuration(你應該得到一個帶有 OpenID Connect 元數據的 JSON 響應)。