首页 文章

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

提问于
浏览
27

先发帖所以要温柔! :)

我正在为Office 365开发一个MVC .NET 5 Web应用程序,并且正在使用OpenIDConnect框架 . 我已经设置了OWIN(3)和ADAL(2)以及我的Azure AD应用程序 . 没有用户操作登录,家庭控制器附加了[Authorize]属性,强制立即登录重定向到Azure AD . 我没有在任何Authorize属性中使用角色 .

The Problem :我可以成功登录我的应用程序 - 一次!首次登录后,我关闭浏览器(或在另一台机器上打开一个新浏览器),然后再次点击该应用程序 . 它将我重定向到我登录的Azure AD登录屏幕,然后它不断在应用程序和Azure之间重定向,直到我得到臭名昭着的400个 Headers 为长期问题 . 查看cookie存储,我发现它是's full of nonces. I check the cache (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,这样我就可以尝试进入Authorize代码并查看_1226949的源代码,但所有发生的事情都是它跳回来进入我自己的代码:(话虽这么说,我已经覆盖了我能做到的事情,发现 filterContext.HttpContext.User.Identity.IsAuthenticated 是假的,这是有道理的,因为这会导致重定向回到Azure登录 .

所以,我知道:

  • Azure正在接受我的登录并返回相关的令牌

  • 在第二次登录时,在OnAuthorization之前,filterContext.HttpContext.User.Identity.IsAuthenticated返回false

  • 我的Azure应用程序配置没问题,或根本不进行身份验证

我觉得:

  • MVC Identity设置中有一些错误 . Azure工作正常或根本不进行身份验证 .

  • 如果您在另一台计算机上执行第二次登录,则问题就出现了,这不是cookie问题

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

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

感谢您提供的任何帮助!

安迪

5 回答

  • 19

    找到了感兴趣的人的答案 . 这是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

  • 1

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

    修复程序位于Microsoft.Owin.Host.SystemWeb 3.1.0.0并使用SystemWebChunkingCookieManager .

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

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

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

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

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

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

  • 0

    我有完全相同的问题 . 由于其他依赖性,无法将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

    以下代码通过在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()
            });
    

相关问题