我正在使用 Finbuckle.Multitenant 和 IdentityServer4 (使用其教程中的标准类和控制器)开发多租户ASP.NET MVC应用程序。我的应用程序为租户(https://host/tenant1/controller/action使用路由策略,并且为每个租户使用单独的cookie(名为 auth.tenant1 , auth.tenant2 ...的cookie等) 。)一切正常,除非我为auth cookie指定自定义路径。如果它们都具有 Path = / ,则一切正常。但是,当我将 Path = / tenant1 设置为名为auth.tenant1的cookie并为其他所有租户设置相同的模式时,在通过同意屏幕后,我会有一个循环重定向。当我在同意屏幕上单击“是”时,我从客户端的IdentityServer中间件获得了302挑战重定向。它将我重定向回“同意”屏幕。在每次“是”之后,我将重新同意。但是,它仅在身份验证过程中发生。如果我打开新标签页并转到https://host/tenant1,则不会被重定向,并且将成功通过身份验证。 搜索了几天的答案,但没有找到任何解决方案。请帮帮我!
这是我客户的 Startup.cs :
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddMultiTenant().WithInMemoryStore(Configuration.GetSection("MultiTenant:InMemoryStore"))
.WithRouteStrategy(MapRoutes)
.WithRemoteAuthentication()
.WithPerTenantOptions<AuthenticationOptions>((options, tenantContext) =>
{
// Allow each tenant to have a different default challenge scheme.
if (tenantContext.Items.TryGetValue("ChallengeScheme", out object challengeScheme))
{
options.DefaultChallengeScheme = (string)challengeScheme;
}
})
.WithPerTenantOptions<CookieAuthenticationOptions>((options, tenantContext) =>
{
options.Cookie.Name += tenantContext.Identifier;
options.Cookie.Path = "/" + tenantContext.Identifier;
options.LoginPath = "/" + tenantContext.Identifier + "/Home/Login";
});
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, o =>
{
o.Cookie.Name = "auth.";
})
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = "https://localhost:5000";
options.RequireHttpsMetadata = true;
options.ClientId = "mvc";
options.SaveTokens = true;
options.ClientSecret = "secret";
//Hybrid protocols (OpenId + OAuth)
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.GetClaimsFromUserInfoEndpoint = true;
//ask to allow access to testApi
options.Scope.Add("testApi");
//allows requesting refresh tokens for long lived API access
options.Scope.Add("offline_access");
options.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProvider = ctx =>
{
var tenant = ctx.HttpContext.GetMultiTenantContext()?.TenantInfo?.Identifier;
ctx.ProtocolMessage.AcrValues = $"tenant:{tenant}";
return Task.FromResult(0);
}
};
});
}
private void MapRoutes(IRouteBuilder router)
{
router.MapRoute("Default", "{__tenant__=tenant1}/{controller=Home}/{action=Index}/{id?}");
}
这是我在IdentityServer端的客户端配置(appsettings.json):
{
"clientId": "mvc",
"clientName": "MVC Client",
"allowedGrantTypes": [ "hybrid", "client_credentials" ],
"clientSecrets": [
{ "value": "K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols=" } // Sha256("secret")
],
"redirectUris": [ "https://localhost:5002/signin-oidc" ],
"postLogoutRedirectUris": [ "https://localhost:5002/signout-callback-oidc" ],
"allowedScopes": [ "openid", "profile", "testApi" ],
"allowOfflineAccess": true
},
我的IdentityServer4配置:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
var identityServerBuilder = services.AddIdentityServer()
.AddDeveloperSigningCredential();
if (_config.GetSection("AppSettings:UseDummyAuthentication").Get<bool>())
{
identityServerBuilder
.AddInMemoryIdentityResources(_config.GetSection("IdentityResources"))
.AddInMemoryApiResources(_config.GetSection("ApiResources"))
.AddInMemoryClients(_config.GetSection("Clients"))
.AddTestUsers(_config.GetSection("TestUsers"));
}
services.AddAuthentication();
}
请帮助我,伙计们!如何在没有重定向同意屏幕的情况下使Path = / tenant1方案正常工作?
答案 0 :(得分:2)
好的,我找出了问题所在,然后将答案发布给将遇到相同问题的人。问题在于,当您更改Cookie的路径时,IdentityServer的中间件找不到,因为它托管在https://host/signin-oidc/上。您需要为客户端上的每个租户处理https://host/tenant1/signin-oidc,并将所有这些URL添加到客户端redirectUris。 为此,您的多租户配置应如下所示
services.AddMultiTenant().WithInMemoryStore(Configuration.GetSection("MultiTenant:InMemoryStore"))
.WithRouteStrategy(MapRoutes)
.WithRemoteAuthentication()
.WithPerTenantOptions<CookieAuthenticationOptions>((options, tenantContext) =>
{
options.Cookie.Name = $"auth.{tenantContext.Identifier}";
options.Cookie.Path = "/" + tenantContext.Identifier;
options.LoginPath = "/" + tenantContext.Identifier + "/Home/Login";
})
.WithPerTenantOptions<OpenIdConnectOptions>((opt, ctx) =>
{
opt.CallbackPath = "/" + ctx.Identifier + "/signin-oidc";
});
Whole Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddMultiTenant().WithInMemoryStore(Configuration.GetSection("MultiTenant:InMemoryStore"))
.WithRouteStrategy(MapRoutes)
.WithRemoteAuthentication()
.WithPerTenantOptions<CookieAuthenticationOptions>((options, tenantContext) =>
{
options.Cookie.Name = $"auth.{tenantContext.Identifier}";
options.Cookie.Path = "/" + tenantContext.Identifier;
options.LoginPath = "/" + tenantContext.Identifier + "/Home/Login";
})
.WithPerTenantOptions<OpenIdConnectOptions>((opt, ctx) =>
{
opt.CallbackPath = "/" + ctx.Identifier + "/signin-oidc";
}); ;
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, o =>
{
o.Cookie.Name = "auth.";
o.Cookie.IsEssential = true;
})
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = "https://localhost:5000";
options.ClientId = "mvc";
options.SaveTokens = true;
options.ClientSecret = "secret";
//Hybrid protocols (OpenId + OAuth)
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.GetClaimsFromUserInfoEndpoint = true;
//ask to allow access to testApi
options.Scope.Add("testApi");
//allows requesting refresh tokens for long lived API access
options.Scope.Add("offline_access");
options.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProvider = ctx =>
{
var tenant = ctx.HttpContext.GetMultiTenantContext()?.TenantInfo?.Identifier;
ctx.ProtocolMessage.AcrValues = $"tenant:{tenant}";
return Task.FromResult(0);
}
};
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
}
else
{
var errorPage = Configuration.GetValue<string>("ErrorPage");
app.UseExceptionHandler(errorPage);
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseMultiTenant();
app.UseAuthentication();
app.UseMvc(MapRoutes);
}
private void MapRoutes(IRouteBuilder router)
{
router.MapRoute("Default", "{__tenant__=tenant1}/{controller=Home}/{action=Index}/{id?}");
}
不要忘记在IdentityServer4 Client.RedirecUris
中注册所有租户URL。"redirectUris": [ "https://localhost:5002/tenant1/signin-oidc", "https://localhost:5002/tenant2/signin-oidc", "https://localhost:5002/tenant3/signin-oidc" ]