Identityserver4 Finbuckle.MultiTenant路由策略和IdentityServer 4租户登录页面重定向

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

我们的目标是构建能够对多个租户进行身份验证的多租户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的配置服务:

    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();