Identityserver4 Finbuckle.MultiTenant路由策略和IdentityServer 4租户登录页面重定向
我们的目标是构建能够对多个租户进行身份验证的多租户identity server4应用程序。我们一直在尝试与IdentityServer4集成以实现这一点。已实施文档中提到的变更,以解决每次请求时的租户问题。当客户端请求IdentityServer的连接/授权端点(路由URL中没有租户标识符)时,identity server重定向到的登录URL将丢失租户标识符。自定义多租户策略成功执行,并按预期方式设置租户信息: http://localhost/Identity/Account/Login 是用来代替http://localhost/tenant1/Account/Login. 我们已经提供了自己的IMultiTrantstrategy实现,以便首先从路由检索租户标识符,如果没有,则检索IdentityServer的请求参数(从请求令牌的客户端添加的自定义参数“租户”)。使用自定义策略成功检索租户标识符,并设置所需的租户信息、存储、策略等,但随后连接/授权端点重定向到http://localhost/Identity/Account/Login 跳过租户模板 但是,如果我们使用,它定义了在所有其他策略失败时要使用的静态租户标识符,则授权端点会将浏览器重定向到http://localhost/tenant1/Account/Login,前提是在FallbackStrategy的静态标识符中使用租户1。下面是代码片段: IdentityServer的配置服务:Identityserver4 Finbuckle.MultiTenant路由策略和IdentityServer 4租户登录页面重定向,identityserver4,asp.net-core-3.1,Identityserver4,Asp.net Core 3.1,我们的目标是构建能够对多个租户进行身份验证的多租户identity server4应用程序。我们一直在尝试与IdentityServer4集成以实现这一点。已实施文档中提到的变更,以解决每次请求时的租户问题。当客户端请求IdentityServer的连接/授权端点(路由URL中没有租户标识符)时,identity server重定向到的登录URL将丢失租户标识符。自定义多租户策略成功执行,并按预期方式设置租户信息: http://localhost/Identity/Account/Login
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>();
services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddControllersWithViews().AddRazorRuntimeCompilation();
services.AddRazorPages(options =>
{
// Since we are using the route multitenant strategy we must add the
// route parameter to the Pages conventions used by Identity.
options.Conventions.AddAreaFolderRouteModelConvention("Identity", "/Account", model =>
{
foreach (var selector in model.Selectors)
{
selector.AttributeRouteModel.Template =
AttributeRouteModel.CombineTemplates("{__tenant__}", selector.AttributeRouteModel.Template);
}
});
});
services.DecorateService<LinkGenerator, AmbientValueLinkGenerator>(new List<string> { "__tenant__" });
services.AddMultiTenant()
.WithStrategy<CustomMultiTenantStrategy>(ServiceLifetime.Transient, "__tenant__") // Looks at route first then specific to idserver4's request
//.WithFallbackStrategy("tenant1") // If added, always redirects to tenant1's login page.
.WithConfigurationStore()
.WithRemoteAuthentication()
.WithPerTenantOptions<AuthenticationOptions>((options, tenantInfo) =>
{
// Allow each tenant to have a different default challenge scheme.
if (tenantInfo.Items.TryGetValue("ChallengeScheme", out object challengeScheme))
{
options.DefaultChallengeScheme = (string)challengeScheme;
// options.DefaultSignOutScheme = (string)challengeScheme;
}
})
.WithPerTenantOptions<CookieAuthenticationOptions>((options, tenantInfo) =>
{
// Since we are using the route strategy configure each tenant
// to have a different cookie name and adjust the paths.
options.Cookie.Path = $"/{tenantInfo.Identifier}";
options.Cookie.Name = $"{tenantInfo.Id}_authentication";
options.LoginPath = $"{options.Cookie.Path}{options.LoginPath}";
options.LogoutPath = $"{options.Cookie.Path}{options.LogoutPath}";
});
// configure identity server with in-memory stores, keys, clients and scopes
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryPersistedGrants()
.AddInMemoryIdentityResources(ResourceStore.GetIdentityResources())
.AddInMemoryApiResources(ResourceStore.GetApiResources())
.AddInMemoryClients(ClientStore.Get())
.AddAspNetIdentity<IdentityUser>()
.AddProfileService<CustomProfileService>();
}
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "http://localhost";
options.RequireHttpsMetadata = false;
options.ClientId = "test-ui";
options.ClientSecret = "XXXXX";
options.ResponseType = "code id_token";
options.Scope.Add("custom-profile");
options.Scope.Add("offline_access");
options.SaveTokens = true;
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = (ctx) =>
{
var tenant = ctx.HttpContext.Request.IsHttps ? "tenant1" : "tenant2"; // Temporary way to swtich between two tenants - the same mvc client is used by different tenants, in prod, it would be identified based on subdomain.
ctx.ProtocolMessage.Parameters.Add("tenant", tenant );
ctx.ProtocolMessage.AcrValues = $"tenant:{tenant}";
return Task.CompletedTask;
}
};
});
services.AddControllersWithViews();
自定义多租户策略:
public class CustomMultiTenantStrategy : IMultiTenantStrategy
{
internal readonly string tenantParam;
private readonly IIdentityServerInteractionService _interactionService;
public CustomMultiTenantStrategy(string tenantParam, IIdentityServerInteractionService interactionService)
{
if (string.IsNullOrWhiteSpace(tenantParam))
{
throw new ArgumentException($"\"{nameof(tenantParam)}\" must not be null or whitespace", nameof(tenantParam));
}
this.tenantParam = tenantParam;
this._interactionService = interactionService;
}
public async Task<string> GetIdentifierAsync(object context)
{
if (!(context is HttpContext))
throw new MultiTenantException(null,
new ArgumentException($"\"{nameof(context)}\" type must be of type HttpContext", nameof(context)));
var httpContext = context as HttpContext;
object identifier = null;
httpContext.Request.RouteValues.TryGetValue(tenantParam, out identifier);
// Fallback to read from the request query params
if (identifier == null)
{
httpContext.Request.Query.TryGetValue("tenant", out StringValues tenant);
if (tenant.Count > 0)
{
identifier = tenant.ToString();
}
else // authorize/connect request would go in here
{
if (httpContext.Request.Query.ContainsKey("returnUrl"))
{
var returnUrl = httpContext.Request.Query["returnUrl"];
var authContext = await this._interactionService.GetAuthorizationContextAsync(returnUrl);
identifier = authContext.Parameters["tenant"];
}
}
}
return await Task.FromResult(identifier as string);
}
}
如果我们从ConfigureServices中删除.WithFallbackStrategy(“tenant1”),则标识服务器将重定向到http://localhost/Identity/Account/Login 结果是404。我们希望根据MVC客户端通过请求参数传递的参数动态设置租户名称。熟悉FinBulk.MultiTenant的任何人都能对此有所了解吗?你已经了解了吗?
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "http://localhost";
options.RequireHttpsMetadata = false;
options.ClientId = "test-ui";
options.ClientSecret = "XXXXX";
options.ResponseType = "code id_token";
options.Scope.Add("custom-profile");
options.Scope.Add("offline_access");
options.SaveTokens = true;
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = (ctx) =>
{
var tenant = ctx.HttpContext.Request.IsHttps ? "tenant1" : "tenant2"; // Temporary way to swtich between two tenants - the same mvc client is used by different tenants, in prod, it would be identified based on subdomain.
ctx.ProtocolMessage.Parameters.Add("tenant", tenant );
ctx.ProtocolMessage.AcrValues = $"tenant:{tenant}";
return Task.CompletedTask;
}
};
});
services.AddControllersWithViews();