web-dev-qa-db-ja.com

ASP.NET_SessionId + OWIN Cookieがブラウザーに送信されません

Owin Cookie認証の使用に関して奇妙な問題があります。

IISを起動すると、IE/FirefoxおよびChromeでサーバー認証が完全に機能します。

認証を使用していくつかのテストを開始し、さまざまなプラットフォームにログインすると、奇妙なエラーが発生しました。散発的に、Owinフレームワーク/ IISは、Cookieをブラウザに送信しません。コードは正しく実行されますが、Cookieがブラウザに配信されないユーザー名とパスワードを入力します。サーバーを再起動すると、サーバーが動作を開始し、ある時点でログインを試行し、Cookieの配信が停止します。コードをステップオーバーしても何も実行されず、エラーもスローされません。

 app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationMode = AuthenticationMode.Active,
            CookieHttpOnly = true,
            AuthenticationType = "ABC",
            LoginPath = new PathString("/Account/Login"),
            CookiePath = "/",
            CookieName = "ABC",
            Provider = new CookieAuthenticationProvider
               {
                  OnApplyRedirect = ctx =>
                  {
                     if (!IsAjaxRequest(ctx.Request))
                     {
                        ctx.Response.Redirect(ctx.RedirectUri);
                     }
                 }
               }
        });

そして私のログイン手順の中で私は次のコードを持っています:

IAuthenticationManager authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
                            authenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);

var authentication = HttpContext.Current.GetOwinContext().Authentication;
var identity = new ClaimsIdentity("ABC");
identity.AddClaim(new Claim(ClaimTypes.Name, user.Username));
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.User_ID.ToString()));
identity.AddClaim(new Claim(ClaimTypes.Role, role.myRole.ToString()));
    authentication.AuthenticationResponseGrant =
        new AuthenticationResponseGrant(identity, new AuthenticationProperties()
                                                   {
                                                       IsPersistent = isPersistent
                                                   });

authenticationManager.SignIn(new AuthenticationProperties() {IsPersistent = isPersistent}, identity);

更新1:問題の原因の1つは、セッションにアイテムを追加したときに問題が始まることです。 Session.Content["ABC"]= 123のような単純なものを追加すると、問題が発生するようです。

1)(Chrome)ログインすると、ASP.NET_SessionId +認証Cookieが取得されます。 2)session.contentsを設定するページに移動します... 3)新しいブラウザ(Firefox)を開き、ログインを試行しますが、ASP.NET_SessionIdを受信せず、認証Cookieを取得しません4)最初のブラウザでASP.NET_SessionIdがあり、引き続き動作します。このCookieを削除すると、IPアドレス(10.x.x.x)とlocalhostで作業している他のすべてのブラウザーと同じ問題が発生します。

更新2: OWINによる認証の前に、まずlogin_loadページでASPNET_SessionIdを強制的に作成します。

1)OWINで認証する前に、ログインページでランダムなSession.Content値を作成してASP.NET_SessionIdを開始します2)その後、認証してさらにセッションを作成します3)他のブラウザが動作するようになりました

これは奇妙です。私は、これがASPとOWINが異なるドメインまたはそのようなものにあると考えていることと関係があると結論付けることができます。

更新-2つの間の奇妙な動作。

追加の奇妙な動作が確認されました-OwinとASPセッションのタイムアウトが異なります。私が見ているのは、何らかのメカニズムにより、OwinセッションがASPセッションよりも長く生きているということです。ログインするとき:1.)cookiedベースの認証セッションがある2.)いくつかのセッション変数を設定する

セッション変数(2)owin cookieセッション変数の前に「死ぬ」と再ログインが強制され、アプリケーション全体で予期しない動作が発生します。 (個人はログインしていますが、実際にはログインしていません)

アップデート3B

少し掘り下げた後、「フォーム」認証タイムアウトとセッションタイムアウトを一致させる必要があるというコメントがページに表示されました。私は通常、2つは同期していると思っていますが、何らかの理由で2つは同期していません。

回避策の概要

1)認証の前に常に最初にセッションを作成します。基本的に、アプリケーションの起動時にセッションを作成しますSession["Workaround"] = 0;

2)[実験的] Cookieを保持する場合は、OWINのタイムアウト/長さがweb.configのsessionTimeoutより長いことを確認してください(テスト中)

135
Piotr Stulinski

同じ問題に遭遇し、OWIN ASP.NETホスティング実装の原因を突き止めました。バグだと思います。

一部の背景

私の調査結果は、これらのアセンブリバージョンに基づいています。

  • Microsoft.Owin、Version = 2.0.2.0、Culture = neutral、PublicKeyToken = 31bf3856ad364e35
  • Microsoft.Owin.Host.SystemWeb、Version = 2.0.2.0、Culture = neutral、PublicKeyToken = 31bf3856ad364e35
  • System.Web、Version = 4.0.0.0、Culture = neutral、PublicKeyToken = b03f5f7f11d50a3a

OWINは独自の抽象化を使用して、応答Cookie(Microsoft.Owin.ResponseCookieCollection)を処理します。この実装は、応答ヘッダーコレクションを直接ラップし、それに応じてSet-Cookieヘッダーを更新します。 OWIN ASP.NETホスト(Microsoft.Owin.Host.SystemWeb)はラップするだけですSystem.Web.HttpResponseおよびヘッダーコレクションです。そのため、OWINを介して新しいCookieが作成されると、応答Set-Cookieヘッダーが直接変更されます。

ただし、ASP.NETは独自の抽象化を使用して、応答Cookieを処理します。これはSystem.Web.HttpResponse.Cookiesプロパティとして公開され、封印されたクラスSystem.Webによって実装されます。 HttpCookieCollection。この実装は、応答Set-Cookieヘッダーを直接ラップしませんが、いくつかの最適化といくつかの内部通知を使用して、変更された状態を応答オブジェクトに明示します。

次に、HttpCookieCollection変更された状態がテストされるリクエストライフタイムの後半点があります(System.Web.HttpResponse.GenerateResponseHeadersForCookies ())およびCookieはSet-Cookieヘッダーにシリアル化されます。このコレクションが特定の状態にある場合、Set-Cookieヘッダー全体が最初にクリアされ、コレクションに格納されているCookieから再作成されます。

ASP.NETセッション実装は、System.Web.HttpResponse.Cookiesプロパティを使用して、ASP.NET_SessionId Cookieを格納します。また、ASP.NETセッション状態モジュールにはいくつかの基本的な最適化があります(System.Web.SessionState.SessionStateModule)s_sessionEverSetという名前の静的プロパティによって実装されます説明。アプリケーションでセッション状態に何かを保存する場合、このモジュールはリクエストごとにもう少し作業を行います。


ログインの問題に戻る

これらすべての要素を使用して、シナリオを説明できます。

ケース1-セッションが設定されなかった

System.Web.SessionState.SessionStateModule、s_sessionEverSetプロパティはfalseです。セッション状態モジュールによってセッションIDは生成されず、System.Web.HttpResponse.Cookies収集状態は変更として検出されないです。この場合、OWIN Cookieはブラウザに正しく送信され、ログインが機能します。

ケース2-セッションはアプリケーションのどこかで使用されましたが、ユーザーが認証を試みる前ではありませんでした

System.Web.SessionState.SessionStateModule、s_sessionEverSetプロパティはtrueです。セッションIDはSessionStateModuleによって生成され、ASP.NET_SessionIdはSystem.Web.HttpResponse.Cookiesコレクションですが、ユーザーのセッションは実際には空であるため、リクエストのライフタイムの後半で削除されます。この場合、System.Web.HttpResponse.Cookies収集状態は変更として検出およびSet-Cookieヘッダーは、Cookieがヘッダー値にシリアル化される前に最初にクリアされます。

この場合、OWIN応答Cookieは「失われ」、ユーザーは認証されず、ログインページにリダイレクトされます。

ケース3-ユーザーが認証を試みる前にセッションが使用されます

System.Web.SessionState.SessionStateModule、s_sessionEverSetプロパティはtrueです。セッションIDはSessionStateModuleによって生成され、ASP.NET_SessionIdはSystem.Web.HttpResponse.CookiesSystem.Web.HttpCookieCollectionおよびSystem.Web.HttpResponse.GenerateResponseHeadersForCookies()Set-Cookieヘッダーは最初にクリアされませんしかし、更新されるだけです。

この場合、OWIN認証CookieとASP.NET_SessionId Cookieの両方が応答で送信され、ログインが機能します。


Cookieに関するより一般的な問題

ご覧のとおり、問題はより一般的であり、ASP.NETセッションに限定されません。 Microsoft.Owin.Host.SystemWebを介してOWINをホストしていて、you/somethingが直接System.Webを使用している場合.HttpResponse.Cookiesコレクションは危険にさらされています。

たとえば、これは機能しますであり、両方のCookieがブラウザに正しく送信されます...

public ActionResult Index()
{
    HttpContext.GetOwinContext()
        .Response.Cookies.Append("OwinCookie", "SomeValue");
    HttpContext.Response.Cookies["ASPCookie"].Value = "SomeValue";

    return View();
}

しかしこれはありませんとOwinCookieは「失われました」...

public ActionResult Index()
{
    HttpContext.GetOwinContext()
        .Response.Cookies.Append("OwinCookie", "SomeValue");
    HttpContext.Response.Cookies["ASPCookie"].Value = "SomeValue";
    HttpContext.Response.Cookies.Remove("ASPCookie");

    return View();
}

両方ともVS2013、IISExpress、およびデフォルトのMVCプロジェクトテンプレートからテストされました。

148
Tomas Dolezal

@TomasDolezalによる優れた分析から始めて、OwinとSystem.Webの両方のソースを見ました。

問題は、System.Webが独自のCookie情報のマスターソースを持ち、それがSet-Cookieヘッダーではないことです。 OwinはSet-Cookieヘッダーのみを知っています。回避策は、Owinによって設定されたすべてのCookieがHttpContext.Current.Response.Cookiesコレクションにも設定されていることを確認することです。

まさにそれを行う小さなミドルウェア( sourcenuget )を作成しました。これは、Cookieミドルウェア登録のすぐ上に配置することを目的としています。

app.UseKentorOwinCookieSaver();

app.UseCookieAuthentication(new CookieAuthenticationOptions());
40
Anders Abel

要するに、.NET CookieマネージャーはOWIN Cookieマネージャーに勝ち、OWINレイヤーに設定されたCookieを上書きします。修正方法は、 ここでKatanaプロジェクトのソリューションとして提供されているSystemWebCookieManagerクラス を使用することです。このクラスまたはそれに類似したクラスを使用する必要があります。これにより、OWINで.NET Cookieマネージャーを使用するようになり、矛盾がなくなります

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

アプリケーションの起動時に、OWIN依存関係を作成するときに割り当てます。

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    ...
    CookieManager = new SystemWebCookieManager()
    ...
});

同様の回答がここに提供されていますが、問題を解決するために必要なすべてのコードベースが含まれているわけではないため、Katana Projectへの外部リンクがあるため、ここに追加する必要がありますダウンする可能性があり、これもここでのソリューションとして完全に記録する必要があります。

31
Alexandru

刀のチームは issue Tomas Dolezarが提起し、投稿しました 回避策に関するドキュメント

回避策は2つのカテゴリに分類されます。 1つは、Response.Cookiesコレクションを使用してOWIN Cookieを上書きしないように、System.Webを再構成することです。もう1つの方法は、影響を受けるOWINコンポーネントを再構成して、CookieをSystem.WebのResponse.Cookiesコレクションに直接書き込むことです。

  • 認証前にセッションが確立されていることを確認します。System.WebCookieとKatana Cookieの競合は要求ごとに発生するため、アプリケーションは認証フローの前に何らかの要求でセッションを確立できる場合があります。これは、ユーザーが最初に到着したときに簡単に実行できるはずですが、セッションCookieまたは認証Cookieの有効期限が切れた場合や更新が必要になった場合に、後で保証するのは難しくなります。
  • SessionStateModuleを無効にします-アプリケーションがセッション情報に依存していないが、セッションモジュールが上記の競合を引き起こすCookieを設定している場合、セッション状態モジュールを無効にすることを検討できます。
  • System.WebのCookieコレクションに直接書き込むようにCookieAuthenticationMiddlewareを再構成します。
app.UseCookieAuthentication(new CookieAuthenticationOptions
                                {
                                    // ...
                                    CookieManager = new SystemWebCookieManager()
                                });

ドキュメントのSystemWebCookieManagerの実装を参照してください(上記のリンク)

詳細 こちら

編集

問題を解決するために行った手順の下。 1.と2.は両方とも問題を個別に解決しましたが、念のため両方を適用することにしました。

1. SystemWebCookieManager を使用します

2.セッション変数を設定します。

protected override void Initialize(RequestContext requestContext)
{
    base.Initialize(requestContext);

    // See http://stackoverflow.com/questions/20737578/asp-net-sessionid-owin-cookies-do-not-send-to-browser/
    requestContext.HttpContext.Session["FixEternalRedirectLoop"] = 1;
}

(補足:上記のInitializeメソッドは、base.InitializeがSessionを使用可能にするため、修正の論理的な場所です。ただし、OpenIdには最初に匿名要求があり、次にOpenIdプロバイダーにリダイレクトしてから戻るため、修正を後で適用することもできますアプリへのリダイレクト後に問題が発生しますが、最初の匿名リクエスト中に修正によりセッション変数が既に設定されているため、リダイレクトが発生する前に問題が修正されます)

Edit 2

Katanaプロジェクト 2016-05-14からコピーアンドペースト:

これを追加:

app.UseCookieAuthentication(new CookieAuthenticationOptions
                                {
                                    // ...
                                    CookieManager = new SystemWebCookieManager()
                                });

...この:

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),
            });
    }
}
16
thomius

回答は既に提供されていますが、owin 3.1.0には、使用できるSystemWebChunkingCookieManagerクラスがあります。

https://github.com/aspnet/AspNetKatana/blob/dev/src/Microsoft.Owin.Host.SystemWeb/SystemWebChunkingCookieManager.cs

https://raw.githubusercontent.com/aspnet/AspNetKatana/c33569969e79afd9fb4ec2d6bdff877e376821b2/src/Microsoft.Owin.Host.SystemWeb/SystemWebChunkingCookieManager.cs

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    ...
    CookieManager = new SystemWebChunkingCookieManager()
    ...
});
3
jonmeyer

自分でOWINミドルウェアにCookieを設定している場合、OnSendingHeadersを使用すると問題が回避されるようです。

たとえば、owinResponseCookie2が設定されていなくても、以下のコードを使用するとowinResponseCookie1が設定されます。

private void SetCookies()
{
    var owinContext = HttpContext.GetOwinContext();
    var owinResponse = owinContext.Response;

    owinResponse.Cookies.Append("owinResponseCookie1", "value1");

    owinResponse.OnSendingHeaders(state =>
    {
        owinResponse.Cookies.Append("owinResponseCookie2", "value2");
    },
    null);

    var httpResponse = HttpContext.Response;
    httpResponse.Cookies.Remove("httpResponseCookie1");
}
3
Appetere

最速の1行コードソリューション:

HttpContext.Current.Session["RunSession"] = "1";

CreateIdentityメソッドの前に次の行を追加してください。

HttpContext.Current.Session["RunSession"] = "1";
var userIdentity = userManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
_authenticationManager.SignIn(new AuthenticationProperties { IsPersistent = rememberLogin }, userIdentity);
2

Set-Cookieヘッダーが送信されないという同じ症状がありましたが、これらの回答はどれも役に立ちませんでした。すべてがローカルマシンで機能しましたが、実稼働環境にデプロイしたときにset-cookieヘッダーが設定されることはありませんでした。

カスタムCookieAuthenticationMiddlewareとWebApiを WebApi圧縮サポート とともに使用する組み合わせであることが判明しました。

幸いなことに、プロジェクトでELMAHを使用していたため、この例外がログに記録されました。

System.Web.HttpExceptionサーバーは、HTTPヘッダーが送信された後にヘッダーを追加できません。

それが私をこれに導いた GitHub Issue

基本的に、私のような奇妙な設定がある場合は、Cookieを設定するWebApiコントローラー/メソッドに対して 圧縮を無効にする にするか、OwinServerCompressionHandlerを試してください。

1
Hugh Jeffner