Второй вход вызывает бесконечный цикл перенаправления после первого успешного входа MVC .NET 5 OWIN ADAL OpenIDConnect

Первый пост, так будь нежнее!:)

Я занимаюсь разработкой веб-приложения MVC .NET 5 для Office 365 и использую платформу OpenIDConnect. Я настроил OWIN (3) и ADAL(2) и приложение Azure AD. Вход в систему не выполняется пользователем, к домашнему контроллеру прикреплен атрибут [Authorize], что вызывает немедленное перенаправление входа в Azure AD. Я не использую роли ни в одном из моих атрибутов Authorize.

Проблема: я могу успешно войти в свои приложения - РАЗ! После первого входа в систему я закрываю браузер (или открываю новый браузер на другом компьютере) и снова запускаю приложение. Он перенаправляет меня на экран входа в Azure AD, на который я вхожу, и затем непрерывно перенаправляет между приложением и Azure, пока я не получу печально известные 400 заголовков для длительной выдачи. Заглянув в магазин печенья, я обнаружил, что в нем полно одноразовых номеров. Я проверяю кэш (рецепт Vittorio EFADALCache, хотя я использовал TokenCache.DefaultShared, когда была обнаружена эта проблема), и он содержит сотни строк данных кэша (только одна строка, сгенерированная с успешным входом).

Я могу видеть, как перенаправления происходят через окно вывода, что новый маркер доступа и обновления генерируется каждый цикл туда и обратно:

Microsoft.IdentityModel.Clients.ActiveDirectory Verbose: 1 : 31/07/2015 12:31:52: 15ad306e-e26d-4827-98dc-dea75853788a - AcquireTokenByAuthorizationCodeHandler: Resource value in the token response was used for storing tokens in the cache
iisexpress.exe Information: 0 : 31/07/2015 12:31:52: 15ad306e-e26d-4827-98dc-dea75853788a - AcquireTokenByAuthorizationCodeHandler: Resource value in the token response was used for storing tokens in the cache
Microsoft.IdentityModel.Clients.ActiveDirectory Information: 2 : 31/07/2015 12:31:52:  - TokenCache: Deserialized 1 items to token cache.
iisexpress.exe Information: 0 : 31/07/2015 12:31:52:  - TokenCache: Deserialized 1 items to token cache.
Microsoft.IdentityModel.Clients.ActiveDirectory Verbose: 1 : 31/07/2015 12:31:52: 15ad306e-e26d-4827-98dc-dea75853788a - TokenCache: Storing token in the cache...
iisexpress.exe Information: 0 : 31/07/2015 12:31:52: 15ad306e-e26d-4827-98dc-dea75853788a - TokenCache: Storing token in the cache...
Microsoft.IdentityModel.Clients.ActiveDirectory Verbose: 1 : 31/07/2015 12:31:52: 15ad306e-e26d-4827-98dc-dea75853788a - TokenCache: An item was stored in the cache
iisexpress.exe Information: 0 : 31/07/2015 12:31:52: 15ad306e-e26d-4827-98dc-dea75853788a - TokenCache: An item was stored in the cache
Microsoft.IdentityModel.Clients.ActiveDirectory Information: 2 : 31/07/2015 12:31:52: 15ad306e-e26d-4827-98dc-dea75853788a - AcquireTokenHandlerBase: === Token Acquisition finished successfully. An access token was retuned:
    Access Token Hash: PN5HoBHPlhhHIf1lxZhEWb4B4Hli69UKgcle0w7ssvo=
    Refresh Token Hash: 3xmypXCO6MIMS9qUV+37uPD4kPip9WDH6Ex29GdWL88=
    Expiration Time: 31/07/2015 13:31:51 +00:00
    User Hash: GAWUtY8c4EKcJnsHrO6NOzwcQDMW64z5BNOvVIl1vAI=

Уведомление AuthorizationCodeReceived в моих OpenIdConnectAuthenticationOptions срабатывает при возникновении проблемы, поэтому я знаю, что Azure считает, что вход выполнен успешно (иначе перенаправление назад в приложение не произойдет):

    private static void PrepO365Auth(IAppBuilder app)
    {

        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

        app.UseCookieAuthentication(new CookieAuthenticationOptions());

        //Configure OpenIDConnect, register callbacks for OpenIDConnect Notifications
        app.UseOpenIdConnectAuthentication(
            new OpenIdConnectAuthenticationOptions
            {

                ClientId = ConfigHelper.ClientId,
                Authority = authority,
                PostLogoutRedirectUri = "https://localhost:44300/Account/SignedOut",
                RedirectUri = "https://localhost:44300/",
                Notifications = new OpenIdConnectAuthenticationNotifications
                {
                    AuthorizationCodeReceived = (context) =>
                    {
                        ClientCredential credential = new ClientCredential(ConfigHelper.ClientId, ConfigHelper.AppKey);
                        string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;

                        AuthenticationContext authContext = new AuthenticationContext(authority, new EFADALTokenCache(signedInUserID)); // TokenCache.DefaultShared Probably need a persistent token cache to handle app restarts etc
                        AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
                            context.Code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, ConfigHelper.GraphResourceId);

                        return Task.FromResult(0);
                    },

                    AuthenticationFailed = context =>
                    {
                        context.HandleResponse();
                        context.Response.Redirect("/Error/ShowError?signIn=true&errorMessage=" + context.Exception.Message);
                        return Task.FromResult(0);
                    }
                }
            });
    }
}

Я заменил (после обнаружения проблемы) атрибут Authorized своим собственным атрибутом Auth, унаследованным от AuthorizeAttribute, просто чтобы я мог попробовать войти в код Authorize и посмотреть, что происходит. Я создал файл PDB из версии исходного кода MVC 5 версии 5, но все, что происходит, это то, что он возвращается к моему собственному коду:(При этом я переопределил то, что мог, и нашел этот filterContext.HttpContext.User.Identity.IsAuthenticated имеет значение false, что имеет смысл, поскольку это может привести к перенаправлению обратно на вход Azure.

Итак, я знаю, что:

  • Azure принимает мой логин и возвращает соответствующие токены
  • При втором входе в систему, перед OnAuthorization, filterContext.HttpContext.User.Identity.IsAuthenticated возвращает false
  • моя конфигурация приложения Azure в порядке, или она не будет аутентифицироваться вообще

Я думаю что:

  • В настройках MVC Identity есть что-то неправильное. Azure работает правильно, иначе он не будет аутентифицироваться вообще.
  • Это не проблема с cookie, поскольку проблема возникает, если вы выполняете второй вход в систему на другом компьютере.

Извините, это немного затянуто, но существует так много таких бесконечных проблем перенаправления, что мне нужно было объяснить, почему моя ситуация была другой!

То, что я ищу (если не ответ!) - это толчок в правильном направлении относительно того, как я могу отлаживать дальше.

Ценю любую помощь, которую вы можете оказать!

Энди

5 ответов

Нашли ответ для всех, кто заинтересован. Это известная ошибка в Katana, когда менеджер cookie Katana и менеджер cookie ASP .NET конфликтуют и перезаписывают файлы cookie друг друга. Полная информация и обходной путь здесь:

http://katanaproject.codeplex.com/wikipage?title=System.Web%20response%20cookie%20integration%20issues&referringTitle=Documentation

Показанный ниже SystemWebCookieManager теперь можно найти в пакете Nuget для https://www.nuget.org/packages/Microsoft.Owin.Host.SystemWeb/.

Добавление кода, когда CodePlex умирает:

//stick this in public void ConfigureAuth(IAppBuilder app)
  app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                // ...
                CookieManager = new SystemWebCookieManager()
            });

//And create this class elsewhere:
public class SystemWebCookieManager : ICookieManager
    {
        public string GetRequestCookie(IOwinContext context, string key)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
            var cookie = webContext.Request.Cookies[key];
            return cookie == null ? null : cookie.Value;
        }

        public void AppendResponseCookie(IOwinContext context, string key, string value, CookieOptions options)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }
            if (options == null)
            {
                throw new ArgumentNullException("options");
            }

            var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);

            bool domainHasValue = !string.IsNullOrEmpty(options.Domain);
            bool pathHasValue = !string.IsNullOrEmpty(options.Path);
            bool expiresHasValue = options.Expires.HasValue;

            var cookie = new HttpCookie(key, value);
            if (domainHasValue)
            {
                cookie.Domain = options.Domain;
            }
            if (pathHasValue)
            {
                cookie.Path = options.Path;
            }
            if (expiresHasValue)
            {
                cookie.Expires = options.Expires.Value;
            }
            if (options.Secure)
            {
                cookie.Secure = true;
            }
            if (options.HttpOnly)
            {
                cookie.HttpOnly = true;
            }

            webContext.Response.AppendCookie(cookie);
        }

        public void DeleteCookie(IOwinContext context, string key, CookieOptions options)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }
            if (options == null)
            {
                throw new ArgumentNullException("options");
            }

            AppendResponseCookie(
                context,
                key,
                string.Empty,
                new CookieOptions
                {
                    Path = options.Path,
                    Domain = options.Domain,
                    Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc),
                });
        }
    }

Я тоже понял это: https://gist.github.com/irwinwilliams/823f43ef8a5e8019a95874049dbb8b00

Я столкнулся с этой проблемой и применил все исправления в Интернете. Никто из них не работал, затем я вошел и посмотрел на мое печенье. Это было огромно. Промежуточное ПО Owin усекало его, а затем атрибут [Authorize] не смог проверить личность -> отправить пользователя в oidc -> identity good - перенаправить на клиента -> truncate cookie -> не может проверить в [Authorize] -> отправить пользователя в oidc -> и т. д.

Исправление было в https://www.nuget.org/packages/Microsoft.Owin.Host.SystemWeb/ и с использованием SystemWebChunkingCookieManager.

Это разделит печенье и проанализирует их вместе.

  app.UseCookieAuthentication(new CookieAuthenticationOptions
  {
      AuthenticationType = "Cookies",
      CookieManager = new Microsoft.Owin.Host.SystemWeb.SystemWebChunkingCookieManager()
  });

У меня не было точно описанной проблемы, но у меня был цикл перенаправления во время входа в систему на основе подключения OpenId, а также на моем компьютере DEV.

В моем случае это была простая ошибка с файлами cookie. Я получал доступ к защищенному URL через HTTP. Убедитесь, что вы получаете доступ к защищенному URL-адресу вашей проверяющей стороны через HTTPS.

После проверки подлинности cookie-файл проверки подлинности будет отправляться только по протоколу HTTPS, это означает, что при доступе к защищенному URL-адресу по HTTP браузер не будет отправлять ваш Auth-файл cookie с запросом, и, следовательно, сервер увидит, что вы не прошли проверку подлинности. В этот момент сервер перенаправит вас на сервер авторизации (где вы уже вошли). Сервер аутентификации перенаправит вас обратно на исходный URL-адрес, тем самым обеспечивая цикл перенаправления.

Это никогда не должно иметь место в ваших развертываниях, потому что вы всегда должны использовать all-SSL в своем приложении, если у вас есть такие функции, как аутентификация. Это снижает риск угона сеанса.

Приведенный ниже код решил мою проблему, добавив события сеанса в файл Golbal.asax.cs.

protected void Session_Start(object sender, EventArgs e)
    {
        // event is raised each time a new session is created     
    }



protected void Session_End(object sender, EventArgs e)
    {
        // event is raised when a session is abandoned or expires

    }

И добавив приведенный ниже код в публичный void метод ConfigureAuth(приложение IAppBuilder) файла Startup.Auth.cs

  app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = "Cookies",
            CookieManager = new Microsoft.Owin.Host.SystemWeb.SystemWebChunkingCookieManager()
        });

У меня была точно такая же проблема. Невозможно изменить URL-адрес с HTTP на HTTPS из-за других зависимостей. Окончательно разрешается путем добавления session_start и session_end в global.asax.cs

  protected void Session_Start(object sender, EventArgs e)
        {
            // event is raised each time a new session is created     
        }

  protected void Session_End(object sender, EventArgs e)
        {
            // event is raised when a session is abandoned or expires

        }
Другие вопросы по тегам