web-dev-qa-db-ja.com

最初のログインが成功した後、2回目のサインインで無限リダイレクトループが発生するMVC .NET 5 OWIN ADAL OpenIDConnect

最初の投稿は優しくしてください! :)

Office 365用のMVC .NET 5 Webアプリを開発していて、OpenIDConnectフレームワークを使用しています。 OWIN(3)とADAL(2)とAzure ADアプリケーションをセットアップしました。ユーザーが操作するログインはありません。ホームコントローラーには[Authorize]属性がアタッチされており、Azure ADへの即時ログインリダイレクトを強制します。 Authorize属性でロールを使用していません。

問題:アプリケーションに正常にログインできます-一度だけ!最初のログイン後、ブラウザーを閉じて(または別のマシンで新しいブラウザーを開いて)、もう一度アプリを実行します。サインインするAzure ADログイン画面にリダイレクトされ、悪意のある400ヘッダーが長い問題になるまで、アプリとAzureの間で継続的にリダイレクトされます。クッキーストアを調べてみると、ノンスでいっぱいであることがわかりました。キャッシュをチェックし(この問題が発見されたときにTokenCache.DefaultSharedを使用していましたが、VittorioのEFADALCacheレシピ)、何百行ものキャッシュデータがあります(1つの行だけがサインインに成功して生成されました)。

出力ウィンドウを介してリダイレクトが発生すると、ラウンドトリップごとに新しいアクセスと更新トークンが生成されることがわかります。

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

(問題の発見後)AuthorizedAttributeをAuthorizeAttributeから継承した独自のAuth属性に置き換えたので、Authorizeコードにステップインして何が起こっているのかを確認できます。私はMVC 5のソースコードのバージョン5バージョンからPDBファイルを構築しましたが、発生するのはそれが自分のコードにジャンプすることだけです:(つまり、私はできることをオーバーライドして、それを見つけましたfilterContext.HttpContext.User.Identity.IsAuthenticatedはfalseです。これにより、Azureサインインにリダイレクトされるため、理にかなっています。

だから、私はそれを知っています:

  • Azureは私のログインを受け入れ、関連するトークンを返します
  • OnAuthorizationの前の2回目のログインで、filterContext.HttpContext.User.Identity.IsAuthenticatedがfalseを返します
  • azureアプリケーションの構成に問題がないか、まったく認証されない

私はそう思います:

  • MVC IDの設定に誤りがあります。 Azureは正常に動作しているか、まったく認証されません。
  • 別のマシンで2回目のログインを実行すると問題が発生するため、Cookieの問題ではありません。

これは少し時間がかかりすぎて申し訳ありませんが、これらの無限リダイレクトの問題は非常に多くあります。私の状況が異なる理由を説明する必要がありました。

私が探しているのは(答えでない場合は!)、さらにデバッグする方法に関する正しい方向へのプッシュです。

あなたが与えることができるどんな助けにも感謝します!

アンディ

28
Andy Bullivent

興味のある人のための答えを見つけました。これはKatanaの既知のバグであり、Katana Cookie ManagerとASP .NET Cookie Managerが競合し、互いの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/823f43ef8a5e8019a95874049dbb8b

20
Andy Bullivent

私はこの問題に遭遇し、インターネット上のすべての修正を適用しました。どれもうまくいかなかったので、私は入り、クッキーを調べました。それは巨大でした。 Owinミドルウェアがそれを切り捨てた後、[Authorize]属性がIDを確認できなかった->ユーザーをoidcに送信-> IDが良い-クライアントにリダイレクト-> Cookieを切り捨て-> [Authorize]で確認できない- >ユーザーをoidcに送る->など.

修正は Microsoft.Owin.Host.SystemWeb 3.1.0. にあり、SystemWebChunkingCookieManagerを使用していました。

Cookieを分割して一緒に解析します。

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

正確に説明された問題はありませんでしたが、OpenId接続ベースのサインイン中にDEVマシンでもリダイレクトループがありました。

私の場合、それはCookieに関する単純なエラーでした。 HTTP経由で保護されたURLにアクセスしていました。依存パーティの保護されたURLにHTTPS経由でアクセスしていることを確認します。

認証が完了すると、認証CookieはHTTPS経由でのみ送信されます。つまり、HTTP経由で保護されたURLにアクセスすると、ブラウザーはリクエストに認証Cookieを送信しないため、サーバーはユーザーを未認証と見なします。この時点で、サーバーは(既にサインインしている)認証サーバーにリダイレクトします。認証サーバーは元のURLにリダイレクトし、リダイレクトループを確実にします。

認証などの機能がある場合は、アプリで常にオールSSLを使用する必要があるため、これはデプロイメントでは当てはまりません。これにより、セッションハイジャックのリスクが軽減されます。

4
Gopal Krishnan

私はまったく同じ問題を抱えていました。他の依存関係のため、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

        }
1
exotic

以下のコードは、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()
        });
0
Dileep Veldi