首页 > 解决方案 > /signin-oidc 多个客户端实例上的 404(数据保护问题)

问题描述

问题

我正在开发一个 .NET Framework 4.7.2 MVC 应用程序,它总是在负载均衡器后面并且工作得很好,除非我添加 MVC 应用程序的第二个实例。

IdentityServer4 本身正在使用数据保护,但非核心客户端存在此问题。

我找不到 .NET 框架的数据保护等效项。有没有,或者我看到了什么不同的东西?

图片

如果我从负载均衡器中删除一个实例,它会再次工作。

最小的工作示例

  app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
                SlidingExpiration = true
            });

            app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
            {
                ClientId = clientId,
                Authority = identityServerUrl,

                //Get this dynamically, register all possible values in IDS.
                //https://github.com/IdentityServer/IdentityServer3/issues/1458
                RedirectUri = redirectUrl,
                PostLogoutRedirectUri = postLogoutRedirectUrl,
                ResponseType = "code id_token",


                Scope = scope,
                SaveTokens = true,
                TokenValidationParameters = new TokenValidationParameters
                {
                    NameClaimType = "name",
                    RoleClaimType = "role"
                },
                RequireHttpsMetadata = false,
                SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
                UseTokenLifetime = false,
                AuthenticationType = "OIDC",

                Notifications = new OpenIdConnectAuthenticationNotifications
                {
                    SecurityTokenValidated = async tokenValidatedNotification =>
                    {
                        try
                        {
                            logger.LogTrace("Entering AuthorizationCodeReceived {authTicketIdentity} {response}",
                                tokenValidatedNotification.AuthenticationTicket.Identity, tokenValidatedNotification.Response.StatusCode);

                            var claimsToKeep = tokenValidatedNotification.AuthenticationTicket.Identity.Claims.ToList();
                            claimsToKeep.Add(new Claim("id_token", tokenValidatedNotification.ProtocolMessage.IdToken));
                            // use the code to get the access and refresh token
#pragma warning disable CS0618 // Type or member is obsolete
                            var tokenClient = new TokenClient($"{identityServerUrl}/connect/token", clientId, clientSecret);
#pragma warning restore CS0618 // Type or member is obsolete

                            TokenResponse tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(
                                tokenValidatedNotification.ProtocolMessage.Code, redirectUrl);

                            if (tokenResponse.IsError)
                            {
                                throw new Exception($"Error authorizing user {tokenResponse.Error}, {tokenResponse.ErrorDescription}", tokenResponse.Exception);
                            }
                            // use the access token to retrieve claims from userinfo
#pragma warning disable CS0618 // Type or member is obsolete
                            var userInfoClient = new UserInfoClient($"{identityServerUrl}/connect/userinfo");
#pragma warning restore CS0618 // Type or member is obsolete
                            UserInfoResponse userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken)
                                .ConfigureAwait(false);

                            if (userInfoResponse.IsError)
                            {
                                throw new Exception(userInfoResponse.Error, userInfoResponse.Exception);
                            }
                            claimsToKeep.AddRange(userInfoResponse.Claims);

                            // create new identity
                            var claimsIdentity = new ClaimsIdentity(tokenValidatedNotification.AuthenticationTicket
                                .Identity
                                .AuthenticationType, "name", "role");
                            claimsIdentity.AddClaims(userInfoResponse.Claims);

                           claimsIdentity.AddClaims(claimsToKeep);

                           var user = claimsIdentity.Claims.FirstOrDefault(x => x.Type == "sub");
                           if (user != null && user.Value.Contains('@'))
                           {
                               var username = user.Value.Split('@')[0];
                               if (!string.IsNullOrEmpty(username))
                               {
                                   //remove existing name claim
                                   var removeClaim = claimsIdentity.Claims.ToList().FirstOrDefault(x => x.Type == "name");
                                   if (removeClaim != null)
                                   {
                                       claimsIdentity.RemoveClaim(removeClaim);
                                   }
                                   //add username as name claim
                                   claimsIdentity.AddClaim(new Claim("name", username.ToLower()));
                               }
                           }

                            tokenValidatedNotification.AuthenticationTicket = new AuthenticationTicket(
                                claimsIdentity,
                                tokenValidatedNotification.AuthenticationTicket.Properties);

                            await Task.FromResult(0);
                        }
                        catch (Exception ex)
                        {
                            logger.LogError(ex, "SecurityTokenValidated error {authTicketIdentity}, {responseStatus} (failure)", tokenValidatedNotification.AuthenticationTicket.Identity, tokenValidatedNotification.Response.StatusCode);
                            throw;
                        }
                    },

                    RedirectToIdentityProvider = async redirectToIdpNotification =>
                    {
                        try
                        {
                            if (!BrowserSupportService.isBrowserSupported())
                            {
                                redirectToIdpNotification.Response.Redirect("/browserNotSupported");
                                redirectToIdpNotification.HandleResponse();
                            }
                            else
                            {
                                // if signing out, add the id_token_hint
                                if (redirectToIdpNotification.ProtocolMessage.RequestType ==
                                    OpenIdConnectRequestType.Logout)
                                {
                                    Claim idTokenHint =
                                        redirectToIdpNotification.OwinContext.Authentication.User.FindFirst("id_token");

                                    if (idTokenHint != null)
                                    {
                                        redirectToIdpNotification.ProtocolMessage.IdTokenHint = idTokenHint.Value;
                                    }

                                    redirectToIdpNotification.ProtocolMessage.PostLogoutRedirectUri =
                                        postLogoutRedirectUrl;
                                    redirectToIdpNotification.Options.PostLogoutRedirectUri = postLogoutRedirectUrl;
                                }

                                if (redirectToIdpNotification.ProtocolMessage.RequestType ==
                                    OpenIdConnectRequestType.Authentication
                                    && IsAjaxRequest(redirectToIdpNotification.Request) &&
                                    redirectToIdpNotification.Response.StatusCode == (int) HttpStatusCode.Unauthorized)
                                {
                                    redirectToIdpNotification.Response.StatusCode = (int) HttpStatusCode.Unauthorized;
                                    redirectToIdpNotification.HandleResponse();
                                }
                            }
                            await Task.FromResult(0);
                        }
                        catch (Exception ex)
                        {
                            logger.LogError(ex, "RedirectToIdentityProvider error");
                            throw;
                        }
                    }
                }
            });

日志文件的相关部分

IdentityServer logs are successful, no errors/warnings during this.

标签: .netasp.net-mvccookiesidentityserver4openid-connect

解决方案


几年前,我在我工作的公司解决了这个问题。原因是默认的 ASP.Net 身份验证 cookie 解密仅适用于创建/加密 cookie 的服务器。

为了解决这个问题,我很确定你只需要在你的 web.config 文件中添加一个机器密钥——并确保它在两台服务器上都是一样的。

作为第一步,也许尝试复制本文中的值 - 如果可行,则稍后生成您自己的密钥: https ://www.iambacon.co.uk/blog/getting-asp-net-authentication-to-work -on-a-web-farm


推荐阅读