ASP.NET Core 2.1 cookie 身份验证似乎具有服务器关联性

Posted

技术标签:

【中文标题】ASP.NET Core 2.1 cookie 身份验证似乎具有服务器关联性【英文标题】:ASP.NET Core 2.1 cookie authentication appears to have server affinity 【发布时间】:2019-04-28 15:53:09 【问题描述】:

我正在 ASP.NET Core 2.1 中开发一个应用程序,并在 Kubernetes 集群上运行它。我已经使用 OpenIDConnect 实现了身份验证,使用 Auth0 作为我的提供者。

这一切都很好。标有[Authorize] 属性的操作或控制器将匿名用户重定向到身份提供者,他们登录,重定向回来,Bob 是你的叔叔。

当我将部署扩展到 2 个或更多容器时,问题开始出现。当用户访问应用程序时,他们会登录,并且根据在回调期间为他们提供服务的容器,身份验证成功或失败。即使在身份验证成功的情况下,当用户点击他们未授权的容器时,反复 F5-ing 最终将重定向到身份提供者。

我的思路是,使用 cookie 身份验证,用户在浏览器中存储一个 cookie,该 cookie 与每个请求一起传递,应用程序对其进行解码并获取 JWT,然后从中获取声明,并且用户已通过身份验证。这使得整个事情无状态,​​因此无论容器服务于请求如何,都应该工作。然而,如上所述,它似乎并没有真正以这种方式工作。

我在Startup.cs 中的配置如下:

services.AddAuthentication(options =>
    
        options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    )
    .AddCookie()
    .AddOpenIdConnect("Auth0", options =>
    
        options.Authority = $"https://Configuration["Auth0:Domain"]";

        options.ClientId = Configuration["Auth0:ClientId"];
        options.ClientSecret = Configuration["Auth0:ClientSecret"];

        options.ResponseType = "code";

        options.Scope.Clear();
        options.Scope.Add("openid");
        options.Scope.Add("profile");
        options.Scope.Add("email");

        options.TokenValidationParameters = new TokenValidationParameters
        
            NameClaimType = "name"
        ;

        options.SaveTokens = true;

        options.CallbackPath = new PathString("/signin-auth0");

        options.ClaimsIssuer = "Auth0";

        options.Events = new OpenIdConnectEvents
        
            OnRedirectToIdentityProviderForSignOut = context =>
            
                var logoutUri =
                    $"https://Configuration["Auth0:Domain"]/v2/logout?client_id=Configuration["Auth0:ClientId"]";

                var postLogoutUri = context.Properties.RedirectUri;
                if (!string.IsNullOrEmpty(postLogoutUri))
                
                    if (postLogoutUri.StartsWith("/"))
                    
                        var request = context.Request;
                        postLogoutUri = request.Scheme + "://" + request.Host + request.PathBase +
                                        postLogoutUri;
                    

                    logoutUri += $"&returnTo=Uri.EscapeDataString(postLogoutUri)";
                

                context.Response.Redirect(logoutUri);
                context.HandleResponse();

                return Task.CompletedTask;
            ,
            OnRedirectToIdentityProvider = context =>
            
                context.ProtocolMessage.SetParameter("audience", "https://api.myapp.com");

                // Force the scheme to be HTTPS, otherwise we end up redirecting back to HTTP in production.
                // They should seriously make it easier to make Kestrel serve over TLS in the same way ngninx does...
                context.ProtocolMessage.RedirectUri = context.ProtocolMessage.RedirectUri.Replace("http://",
                    "https://", StringComparison.OrdinalIgnoreCase);

                Debug.WriteLine($"RedirectURI: context.ProtocolMessage.RedirectUri");

                return Task.FromResult(0);
            
        ;
    );

我花了几个小时试图解决这个问题,但结果一无所获。我现在唯一能想到的理论上可行的方法是使用粘性负载平衡,但这更多的是应用创可贴而不是实际解决问题。

使用 Kubernetes 的主要原因之一是它的弹性和处理扩展的能力。就目前而言,我只能扩展我的支持服务,而我的主应用程序必须作为单个 pod 运行。这远非理想。

也许某处有某种机制可以与我不知道的特定实例建立关联?

我希望有人能指出我正确的方向。

谢谢!

【问题讨论】:

clientkey能不能成为key? 如何保存token?在服务器上还是在数据库中? 【参考方案1】:

通过身份验证发布的 cookie 通过数据保护进行加密。默认情况下,数据保护的范围仅限于特定应用程序或其实例。如果您需要在实例之间共享 auth cookie,则需要确保数据保护密钥保存到一个公共位置并且应用程序名称相同。

services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"))
    .SetApplicationName("MyApp");

您可以在docs 中找到更多信息。

【讨论】:

你是绝对的救星!我将其设置为保留在 Azure Blob 存储中。考虑到密钥所在的容器是私有的,并且 SAS 是保密的,是否需要对密钥进行加密?对我来说这似乎是多余的,因为由于 HTTPS,密钥在传输中被加密,唯一的另一种方法是访问 Azure 门户,此时加密也无济于事? 始终加密静态密钥。我不记得现在是哪家公司了,但新闻中只有一个故事,说一家公司泄露了他们的存储空间。永远不要假设存储是安全的。 @aevitas 您可能希望考虑一个额外的选项。 Kubernetes 可以配置为基于每个客户端自动处理用户的会话亲和性。从本质上讲,这将导致给定用户根据您选择的标准被发送到容器的同一实例。 @MatthewBrubaker:这就是所谓的“粘性会话”,它们是一种反模式。它在一定程度上抵消了拥有多个实例的好处,并完全扼杀了故障转移。 更新了Docs的链接【参考方案2】:

每当我重新启动我的 Azure 应用服务 (PaaS) 并且我的用户的 cookie 不再有效时,我都会遇到同样的问题。我的应用使用了 ASP.NET Core Identity 框架。

以下文档解释了将数据保护配置为跨多个应用实例甚至多个 Web 应用范围的各种方法:

https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview

我发现使用 blob 存储帐户是使其工作的最快方法:

var storageAccount = CloudStorageAccount.Parse(configuration["Configuration key to Azure storage connection string"]);
var client = storageAccount.CreateCloudBlobClient();
var container = client.GetContainerReference("key-container");

container.CreateIfNotExistsAsync().GetAwaiter().GetResult();

services.AddDataProtection()
    .SetApplicationName("Application Name")
    .PersistKeysToAzureBlobStorage(container, "keys.xml");

【讨论】:

以上是关于ASP.NET Core 2.1 cookie 身份验证似乎具有服务器关联性的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET Core2.2 和2.1 版本中对cookie的设置和存储

ASP.NET Core 2.1 cookie 身份验证似乎具有服务器关联性

ASP.NET Core 2.1 MVC - 无法设置从 Web api 响应接收到的 cookie

带有 Angular 6 的 Asp.Net Core 2.1 中的 Windows 身份验证 - Chrome 连续登录提示

Asp.net core 3.1 保护 API 和 Web 应用程序

详解Asp.Net Core中的Cookies