【问题标题】:Endless circular redirect with IdentityServer4使用 IdentityServer4 进行无休止的循环重定向
【发布时间】:2018-12-03 16:33:32
【问题描述】:

我正在使用 Finbuckle.MultitenantIdentityServer4 开发多租户 ASP.NET MVC 应用程序(使用他们教程中的标准类和控制器)。我的应用程序使用租户的路由策略 (https://host/tenant1/controller/action),并且我为每个租户使用单独的 cookie(名为 auth.tenant1auth.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 方案在同意屏幕上没有重定向的情况下工作???

【问题讨论】:

    标签: asp.net .net authentication identityserver4 openid-connect


    【解决方案1】:

    好的,我找出了问题所在,并将答案发布给遇到同样问题的人。问题是当您更改 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";
                });
    

    整个 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 中注册所有租户 url Client.RedirecUris

    "redirectUris": [ "https://localhost:5002/tenant1/signin-oidc", "https://localhost:5002/tenant2/signin-oidc", "https://localhost:5002/tenant3/signin-oidc" ]
    

    【讨论】:

    • 嗨。我使用与您相同的方法,但我被困在一点上。当我尝试访问tenant1 时,系统将我重定向到身份服务(由 IdentityServer4 和 ASP.NET Core Identity 框架实现)登录页面。成功登录后,它会将我重定向回 MVC 应用程序。现在,当我尝试访问另一个租户时,例如租户2,它将我重定向到身份服务器的“授权”端点。它实际上是使用 asp.net 核心身份 cookie 将用户重定向到授权端点。你能帮我如何让用户登陆登录页面吗?
    • 嗨,@BilalHashmi 没有代码很难说,但我认为在访问租户 2 后,您的用户应该在同意屏幕上被重定向。一旦允许,租户 2 应该在未经同意屏幕的情况下被允许。这是正常行为,我认为用户不应该每次都登录。但如果你愿意,你应该使用你的会话 cookie。它应该是特定于租户域的。请往那个方向看看
    • 感谢您的反馈。我通过在 IdentityServer4 端监听身份 cookie 选项上的事件 (OnValidatePrincipal) 解决了这个问题。在事件处理程序中,我比较了在 acr_values 中收到的用户关联租户和租户信息。如果不匹配,我拒绝委托人。
    • @BilalHashmi 很棒 :)
    猜你喜欢
    • 2013-07-02
    • 1970-01-01
    • 2019-09-20
    • 2018-07-23
    • 2016-02-09
    • 2019-11-01
    • 1970-01-01
    • 2021-12-30
    • 1970-01-01
    相关资源
    最近更新 更多