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

Posted

技术标签:

【中文标题】第一次成功登录 MVC .NET 5 OWIN ADAL OpenIDConnect 后,第二次登录导致无限重定向循环【英文标题】:Second sign-in causes infinite redirect loop after the first successful login MVC .NET 5 OWIN ADAL OpenIDConnect 【发布时间】:2015-10-23 04:22:29 【问题描述】:

第一次发帖,所以要温柔! :)

我正在为 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 身份设置中有问题。 Azure 工作正常,否则根本无法进行身份验证。 这不是 cookie 问题,因为如果您在另一台计算机上执行第二次登录就会出现问题

很抱歉,这有点啰嗦,但是那里有很多这样的无限重定向问题,我需要解释为什么我的情况不同!

我正在寻找的(如果不是答案!)是朝着正确的方向推动我如何进一步调试。

感谢您提供的任何帮助!

安迪

【问题讨论】:

已经为任何感兴趣的人找到了答案。这是 Katana 中的一个已知错误,其中 Katana cookie 管理器和 ASP .NET cookie 管理器发生冲突并覆盖彼此的 cookie。完整的详细信息和解决方法在这里:katanaproject.codeplex.com/… 看起来这个问题在 ASP.NET 4 中不会得到解决,因为它今天仍然是一个问题。 @AndyBullivent 请将您的评论作为答案。 @AndyBullivent - 你解决过这个问题吗?我们看到一个间歇性的重定向循环,我很想知道这是如何解决的。 【参考方案1】:

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

就我而言,这是一个简单的 cookie 错误。我正在通过 HTTP 访问受保护的 URL。确保您通过 HTTPS 访问依赖方上受保护的 URL。

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

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

【讨论】:

【参考方案2】:

已经为任何感兴趣的人找到了答案。这是 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

【讨论】:

谢谢!!彻底解决了我的问题!我有带有owin auth中间件的MVC项目,首先是cookie auth,然后是oidc。一旦我将 Authorize 属性放在任何东西上,我就会得到无限循环,我可以看到 cookie auth 进入 ResponseSignedIn。似乎 System.Web 正在替换 cookie。我确实注册了 proc session 并读到 Session 很可能是一个罪犯......无论如何,再次感谢! 两个脚趾救命...谢谢 谢谢我在过去的一周里一直在用这些结构。现在它工作正常:)【参考方案3】:

我遇到了完全相同的问题。由于其他依赖关系,无法将 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

        

【讨论】:

【参考方案4】:

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

修复在 Microsoft.Owin.Host.SystemWeb 3.1.0.0 中并使用 SystemWebChunkingCookieManager。

它将 cookie 拆分并一起解析。

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

【讨论】:

不确定@Cyber​​pks 必须解决什么问题,但这是主要问题的完美解决方案。感谢 Scott Belchak 的一百万!!!【参考方案5】:

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

    

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

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

【讨论】:

以上是关于第一次成功登录 MVC .NET 5 OWIN ADAL OpenIDConnect 后,第二次登录导致无限重定向循环的主要内容,如果未能解决你的问题,请参考以下文章

MVC5 - ASP.NET Identity登录原理 - Claims-based认证和OWIN

在 ASP.NET MVC5 中使用 Steam 登录

Asp.Net MVC 5 Owin Twitter Auth 抛出 401 异常

在弹出窗口中使用 Owin 外部登录的 Asp.net 登录提供程序

如何在 mvc 5 中知道 OWIN cookie/用户会话即将到期

ASP.Net MVC 5 SPA 模板 OWin /token 导致“invalid_client”