ASP.NET Core 2.1 cookie 身份验证似乎具有服务器相​​关性 [英] ASP.NET Core 2.1 cookie authentication appears to have server affinity

查看:19
本文介绍了ASP.NET Core 2.1 cookie 身份验证似乎具有服务器相​​关性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm developing an application in ASP.NET Core 2.1, and running it on a Kubernetes cluster. I've implemented authentication using OpenIDConnect, using Auth0 as my provider.

这一切正常.用 [Authorize] 属性标记的动作或控制器将匿名用户重定向到身份提供者,他们登录,重定向回来,鲍勃是你的叔叔.

This all works fine. Actions or controllers marked with the [Authorize] attribute redirect anonymous user to the identity provider, they log in, redirects back, and Bob's your uncle.

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

The problems start occurring when I scale my deployment to 2 or more containers. When a user visits the application, they log in, and depending on what container they get served during the callback, authentication either succeeds or fails. Even in the case of authentication succeeding, repeatedly F5-ing will eventually redirect to the identity provider when the user hits a container they aren't authorized on.

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

My train of thought on this would be that, using cookie authentication, the user stores a cookie in their browser, that gets passed along with each request, the application decodes it and grabs the JWT, and subsequently the claims from it, and the user is authenticated. This makes the whole thing stateless, and therefore should work regardless of the container servicing the request. As described above however, it doesn't appear to actually work that way.

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

My configuration in Startup.cs looks like this:

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

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

I've spent hours trying to address this issue, and came up empty. The only thing I can think of that could theoretically work now is using sticky load balancing, but that's more applying a band-aid than actually fixing the problem.

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

One of the main reasons to use Kubernetes is its resilience and ability to handle scaling very well. As it stands, I can only scale my backing services, and my main application would have to run as a single pod. That's far from ideal.

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

Perhaps there is some mechanism somewhere that creates affinity with a specific instance that I'm not aware of?

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

I hope someone can point me in the right direction.

谢谢!

推荐答案

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

The cookie issued by authentication is encrypted via Data Protection. Data Protection by default is scoped to a particular application, or instance thereof. If you need to share an auth cookie between instances, you need to ensure that the data protection keys are persisted to a common location and that the application name is the same.

services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(@"\serversharedirectory"))
    .SetApplicationName("MyApp");

您可以在 文档.

这篇关于ASP.NET Core 2.1 cookie 身份验证似乎具有服务器相​​关性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆