在第一次成功登录MVC .NET 5 OWIN ADAL OpenIDConnect后,第二次登录会导致无限重定向循环

问题描述 投票:28回答:5

先发帖所以要温柔! :)

我正在为Office 365开发一个MVC .NET 5 Web应用程序,并且正在使用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=

当问题发生时,我的OpenIdConnectAuthenticationOptions中的AuthorizationCodeReceived通知正在被点击,所以我知道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);
                    }
                }
            });
    }
}

我已经使用我自己的Auth属性替换了(在发现问题之后)Authorized属性,继承自AuthorizeAttribute,这样我就可以尝试进入授权代码并查看发生了什么。我从MVC 5的源代码的第5版开始构建了一个PDB文件,但所有发生的事情都是它跳回到我自己的代码中:(话虽这么说,我已经覆盖了我能做到的并找到了filterContext.HttpContext。 User.Identity.IsAuthenticated是false,这是有道理的,因为这会导致重定向回Azure登录。

所以,我知道:

  • Azure正在接受我的登录并返回相关的令牌
  • 在第二次登录时,在OnAuthorization之前,filterContext.HttpContext.User.Identity.IsAuthenticated返回false
  • 我的Azure应用程序配置很好,或者根本不会进行身份验证

我觉得:

  • MVC Identity设置中有一些错误。 Azure工作正常或根本不进行身份验证。
  • 如果您在另一台计算机上执行第二次登录,则问题就出现了,这不是cookie问题

对不起,这有点长篇大论,但有很多这些无限重定向问题,我需要解释为什么我的情况不同!

我正在寻找的(如果不是答案!)是一个正确的方向,我可以进一步调试。

感谢您提供的任何帮助!

安迪

asp.net-mvc azure asp.net-mvc-5 owin openid-connect
5个回答
20
投票

找到了感兴趣的人的答案。这是Katana中的一个已知错误,其中Katana cookie管理器和ASP .NET cookie管理器冲突并覆盖彼此的cookie。完整的详细信息和解决方法:

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

现在可以在Microsoft.Owin.Host.SystemWeb Nuget包中找到如下所示的SystemWebCookieManager。

添加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


5
投票

我遇到了这个问题并在互联网上应用了所有修复程序。他们都没有工作,然后我进去看了我的饼干。这是巨大的。 Owin中间件正在截断它,然后[授权]属性无法验证身份 - >将用户发送到oidc - >身份好 - 重定向到客户端 - >截断cookie - >无法验证[授权] - >将用户发送到oidc - >等

该修复程序位于Microsoft.Owin.Host.SystemWeb 3.1.0.0并使用System Web ChunkingCookieManager。

它将分割cookie并将它们解析在一起。

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

3
投票

我没有完全描述的问题,但我确实在我的DEV机器上基于OpenId连接的登录期间有一个重定向循环。

在我的情况下,这是一个简单的错误与cookie。我通过HTTP访问受保护的URL。确保您通过HTTPS访问依赖方的受保护URL。

一旦通过身份验证,身份验证cookie将仅通过HTTPS发送,这意味着当您通过HTTP访问受保护的URL时,浏览器将不会发送带有请求的身份验证cookie,因此服务器会将您视为未经身份验证。此时,服务器会将您重定向到auth服务器(您已经登录的位置)。 Auth服务器会将您重定向回原始URL,从而确保重定向循环。

在您的部署中绝不应该这样,因为如果您具有身份验证等功能,则应始终在应用中使用全SSL。这降低了会话劫持的风险。


1
投票

我有完全相同的问题。由于其他依赖性,无法将URL从HTTP更改为HTTPS。最后通过在global.asax.cs中添加session_start和session_end来解决

  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

        }

0
投票

以下代码通过在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

    }

并通过在Startup.Auth.cs文件的public void ConfigureAuth(IAppBuilder app)方法中添加以下代码

  app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = "Cookies",
            CookieManager = new Microsoft.Owin.Host.SystemWeb.SystemWebChunkingCookieManager()
        });
© www.soinside.com 2019 - 2024. All rights reserved.