wake-up-neo.com

Die zweite Anmeldung verursacht eine unendliche Umleitungsschleife nach der ersten erfolgreichen Anmeldung. MVC .NET 5 OWIN ADAL OpenIDConnect

erster post also sei sanft! :)

Ich entwickle eine MVC .NET 5-Web-App für Office 365 und verwende das OpenIDConnect-Framework. Ich habe OWIN (3) und ADAL (2) und meine Azure AD-Anwendung eingerichtet. Es gibt keine vom Benutzer durchgeführte Anmeldung. Der Home-Controller verfügt über ein Attribut [Authorize], das die sofortige Umleitung der Anmeldung an Azure AD erzwingt. Ich verwende keine Rollen in einem meiner Authorize-Attribute.

Das Problem: Ich kann mich erfolgreich bei meinen Anwendungen anmelden - EINMAL! Nach der ersten Anmeldung schließe ich den Browser (oder öffne einen neuen Browser auf einem anderen Computer) und klicke erneut auf die App. Es leitet mich zum Azure AD-Anmeldebildschirm weiter, in den ich mich einlogge, und leitet dann kontinuierlich zwischen der App und Azure um, bis ich die berüchtigten 400 Header zu einem langen Problem bekomme. Wenn ich in den Keksladen schaue, finde ich, dass es voller Nonces ist. Ich überprüfe den Cache (das EFADALCache-Rezept von Vittorio, obwohl ich TokenCache.DefaultShared verwendet habe, als dieses Problem entdeckt wurde) und es hat Hunderte von Zeilen mit Cache-Daten (nur eine Zeile wurde mit einer erfolgreichen Anmeldung generiert). 

Ich kann bei den Redirects über das Ausgabefenster erkennen, dass bei jedem Roundtrip ein neues Zugriffs- und Aktualisierungstoken generiert wird:

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=

Die AuthorizationCodeReceived-Benachrichtigung in meinen OpenIdConnectAuthenticationOptions-Ereignissen wird getroffen, wenn das Problem auftritt. Daher weiß ich, dass Azure der Meinung ist, dass die Anmeldung erfolgreich war (andernfalls würde die Zurückleitung zur App nicht erfolgen):

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

Ich habe (nachdem ich das Problem entdeckt hatte) das Authorized-Attribut durch mein eigenes Auth-Attribut ersetzt, das von AuthorizeAttribute erbt. Nur so konnte ich versuchen, in den Authorize-Code einzusteigen und zu sehen, was passiert. Ich habe eine PDB-Datei aus der Version 5-Version des Quellcodes von MVC 5 erstellt, aber alles, was passiert, ist, dass sie in meinen eigenen Code zurückspringt :( Davon abgesehen, habe ich das überschrieben, was ich konnte, und filterContext. HttpContext.User.Identity.IsAuthenticated ist false, was sinnvoll ist, da dies die Umleitung zur Azure-Anmeldung bewirken würde. 

Also ich weiß das:

  • Azure akzeptiert mein Login und gibt die entsprechenden Token zurück
  • Bei der zweiten Anmeldung vor OnAuthorization gibt der filterContext.HttpContext.User.Identity.IsAuthenticated den Wert false zurück
  • meine Azure-Anwendungskonfiguration ist in Ordnung oder wurde überhaupt nicht authentifiziert

Ich denke, dass:

  • Im MVC Identity-Setup ist etwas falsch. Azure funktioniert ordnungsgemäß oder wird überhaupt nicht authentifiziert.
  • Es handelt sich nicht um ein Cookie-Problem, da das Problem auftritt, wenn Sie die zweite Anmeldung auf einem anderen Computer durchführen

Es tut mir leid, dass dies ein bisschen langweilig ist, aber es gibt so viele dieser endlosen Redirect-Probleme, dass ich erklären musste, warum meine Situation anders war!

Was ich suche (wenn nicht eine Antwort!) Ist ein Push in die richtige Richtung, wie ich weiter debuggen kann.

Schätzen Sie jede Hilfe, die Sie geben können!

Andy

28
Andy Bullivent

Habe die Antwort für alle Interessierten gefunden. Es ist ein bekannter Fehler in Katana, bei dem der Katana-Cookie-Manager und der .NET-Cookie-Manager ASP zusammenstoßen und die Cookies des anderen überschreiben. Vollständige Details und Problemumgehung hier: 

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

Der unten abgebildete SystemWebCookieManager befindet sich jetzt im Paket Microsoft.Owin.Host.SystemWeb Nuget.

Hinzufügen des Codes für den Zeitpunkt, zu dem CodePlex stirbt: 

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

Ich habe auch eine Gist daraus gemacht: https://Gist.github.com/irwinwilliams/823f43ef8a5e8019a95874049dbb8b00

19
Andy Bullivent

Ich bin auf dieses Problem gestoßen und habe ALLE FIXES IM INTERNET angewendet. Keiner von ihnen arbeitete, dann ging ich hinein und schaute auf meinen Keks. Es war groß. Die Owin-Middleware schnitt es ab, und dann konnte das Attribut [Authorize] die Identität nicht überprüfen. -> Benutzer an oidc senden. > Benutzer an oidc senden -> usw.

Das Update war in Microsoft.Owin.Host.SystemWeb 3.1.0.0 und verwendet den SystemWebChunkingCookieManager.

Es teilt die Kekse auf und analysiert sie zusammen.

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

Ich hatte nicht genau das beschriebene Problem, aber ich hatte auch eine Umleitungsschleife während der OpenId-Connect-basierten Anmeldung auf meinem DEV-Computer. 

In meinem Fall war es ein einfacher Fehler mit Cookies. Ich habe über HTTP auf die geschützte URL zugegriffen. Stellen Sie sicher, dass Sie über HTTPS auf die geschützte URL Ihrer vertrauenden Seite zugreifen. 

Sobald Sie authentifiziert sind, wird das Authentifizierungs-Cookie nur über HTTPS gesendet. Wenn Sie also über HTTP auf eine geschützte URL zugreifen, sendet der Browser kein Authentifizierungs-Cookie mit der Anforderung. Daher werden Sie vom Server als nicht authentifiziert angezeigt. An diesem Punkt werden Sie vom Server zum Authentifizierungsserver umgeleitet (auf dem Sie bereits angemeldet sind). Der Auth-Server leitet Sie zur ursprünglichen URL zurück und sorgt so für eine Umleitungsschleife.

Dies sollte in Ihren Bereitstellungen niemals der Fall sein, da Sie in Ihrer App immer All-SSL verwenden sollten, wenn Sie über Funktionen wie Authentifizierung verfügen. Dies verringert das Risiko von Session-Hijacking. 

1
Gopal

Ich hatte genau das gleiche Problem. Die URL konnte aufgrund anderer Abhängigkeiten nicht von HTTP in HTTPS geändert werden. Endlich durch Hinzufügen von session_start und session_end in global.asax.cs aufgelöst

  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

        }
1
exotic

Mit dem folgenden Code wurde mein Problem behoben, indem Sitzungsereignisse in der Datei Golbal.asax.cs hinzugefügt wurden.

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

    }

Und durch Hinzufügen des folgenden Codes in der öffentlichen void-Methode ConfigureAuth (IAppBuilder) der Datei Startup.Auth.cs 

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