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

标签 asp.net-mvc azure asp.net-mvc-5 owin openid-connect

第一次发帖,请温柔一点! :)

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

问题:我可以成功登录我的应用程序 - 一次!第一次登录后,我关闭浏览器(或在另一台计算机上打开新浏览器),然后再次点击该应用程序。它将我重定向到我登录的 Azure AD 登录屏幕,然后它不断在应用程序和 Azure 之间重定向,直到我收到臭名昭著的 400 header 到长问题。看看 cookies 店,我发现里面充满了随机数。我检查了缓存(Vittorio 的 EFADALCache 配方,尽管发现此问题时我正在使用 TokenCache.DefaultShared),它有数百行缓存数据(成功登录后仅生成一行)。

我可以看到,当通过输出窗口发生重定向时,每次往返都会生成一个新的访问和刷新 token :

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 属性(继承自 AuthorizeAttribute)替换了 Authorized 属性,这样我就可以尝试进入 Authorize 代码并查看发生了什么。我从 MVC 5 源代码的版本 5 构建了一个 PDB 文件,但所发生的只是它跳回我自己的代码:(话虽这么说,我已经覆盖了我可以覆盖的内容,并发现 filterContext .HttpContext.User.Identity.IsAuthenticated 为 false,这是有道理的,因为这会导致重定向回 Azure 登录。

所以,我知道:

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

我认为:

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

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

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

感谢您提供的任何帮助!

安迪

最佳答案

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

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

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

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

关于asp.net-mvc - 第一次成功登录后第二次登录导致无限重定向循环 MVC .NET 5 OWIN ADAL OpenIDConnect,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31747008/

相关文章:

javascript - Microsoft Jscript Angular 未定义

html - CSS ASP.NET MVC 样式不工作

html - 如果没有javascript没有返回内容,如何隐藏iframe

azure - 使用自动化帐户从 azure key Vault 安装 ssl 证书

html - 单击按钮时显示 Bootstrap 警报

c# - 试图了解如何将变量传递给自定义验证?

css - 使用 CSS 变量时 VS MVC 元素 CSS 缩小失败

azure - 通过 Multi-Tenancy Azure AD 应用程序中的访问 token 唯一标识用户

azure - KQL 分组依据与拖放分组功能的结果相同

c# - 功能 'interpolated strings' 在 C# 5 中不可用。请使用语言版本 6 或更高版本。