ASP.NET_SessionId + OWIN Cookies不会发送到浏览器

时间:2013-12-23 05:39:35

标签: asp.net asp.net-identity owin katana

使用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:似乎问题的一个原因是当我向会话添加项目时问题就开始了。添加像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有关,认为它们位于不同的域或类似的东西。

更新3 - 两者之间存在奇怪的行为。

确定了其他奇怪的行为 - Owin和ASP会话超时不同。我所看到的是,通过某种机制,我的Owin会话比我的ASP会话活得更长。所以登录时: 1.)我有一个基于烹饪的auth会话 2.)我设置了几个会话变量

我的会话变量(2)在owin cookie会话变量强制重新登录之前“死亡”,这会在整个应用程序中导致意外行为。 (人员已登录但尚未登录)

更新3B

经过一番挖掘后,我在一个页面上看到一些评论说“格式”认证超时和会话超时需要匹配。我通常认为两者是同步的,但无论出于何种原因,两者都不同步。

解决方法摘要

1)在认证之前始终首先创建会话。基本上在启动应用程序Session["Workaround"] = 0;

时创建会话

2)[实验]如果你坚持使用cookies,请确保你的OWIN超时/长度比web.config中的sessionTimeout长(在测试中)

9 个答案:

答案 0 :(得分:146)

我遇到了同样的问题,并将原因追溯到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。会话状态模块和 System.Web.HttpResponse.Cookies 集合状态未检测到已更改,不会生成任何会话ID。在这种情况下,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.Cookies 。由于 System.Web.HttpCookieCollection System.Web.HttpResponse.GenerateResponseHeadersForCookies() 中的内部优化,所以不首先清除Set-Cookie标头但只有更新。

在这种情况下,响应和登录都会发送OWIN身份验证cookie和ASP.NET_SessionId cookie。


更常见的Cookie问题

正如您所看到的,问题更为笼统,并不仅限于ASP.NET会话。如果您通过 Microsoft.Owin.Host.SystemWeb 托管OWIN,并且您/某些东西直接使用 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项目模板进行了测试。

答案 1 :(得分:39)

从@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());

答案 2 :(得分:27)

简而言之, .NET cookie管理器将赢得OWIN cookie管理器并覆盖在OWIN层上设置的cookie 。解决方法是使用SystemWebCookieManager class, provided as a solution on the Katana Project here。您需要使用此类或类似的类,强制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项目的外部链接可能会崩溃这应该完全记录下来作为解决方案。

答案 3 :(得分:15)

Katana团队回答了issue Tomas Dolezar的提议,并发布了documentation about workarounds

  

变通办法分为两类。一个是重新配置   System.Web因此它避免使用Response.Cookies集合和   覆盖OWIN cookie。另一种方法是重新配置   受影响的OWIN组件,因此他们直接写入cookie   System.Web的Response.Cookies集合。

     
      
  • 确保在身份验证之前建立会话:System.Web和Katana Cookie之间的冲突是按请求进行的,因此可能是   应用程序可以根据某些请求建立会话   在认证流程之前。这应该很容易做到   用户第一次到达,但以后可能更难保证   会话或身份验证cookie过期和/或需要刷新。
  •   
  • 禁用SessionStateModule - 如果应用程序不依赖会话信息,但会话模块仍在设置   导致上述冲突的cookie,您可以考虑禁用   会话状态模块。
  •   
  • 重新配置CookieAuthenticationMiddleware以直接写入System.Web的cookie集合。
  •   
app.UseCookieAuthentication(new CookieAuthenticationOptions
                                {
                                    // ...
                                    CookieManager = new SystemWebCookieManager()
                                });

请参阅文档(上面的链接)

中的SystemWebCookieManager实现

更多信息here

修改

我们采取的步骤解决了这个问题。 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提供程序,然后回到应用程序。重定向到应用程序后问题会发生,而修复程序在第一个匿名请求期间已经设置了会话变量,从而在重定向重新发送之前解决了问题)

修改2

来自Katana project 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),
            });
    }
}

答案 4 :(得分:3)

如果您自己在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");
}

答案 5 :(得分:3)

已经提供了答案,但是在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()
    ...
});

答案 6 :(得分:1)

我有没有发送Set-Cookie标头的相同症状,但这些答案都没有帮助我。一切都在我的本地机器上运行,但是当部署到生产环境时,set-cookie标头永远不会被设置。

事实证明,这是使用自定义CookieAuthenticationMiddleware与WebApi以及WebApi compression support

的组合

幸运的是我在我的项目中使用了ELMAH,这让我记录了这个异常:

  

System.Web.HttpException服务器无法在HTTP之后追加标头   标题已发送。

这让我想到了这个GitHub Issue

基本上,如果您有类似我的奇怪设置,您将需要disable compression来设置Cookie的WebApi控制器/方法,或者尝试OwinServerCompressionHandler

答案 7 :(得分: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);

答案 8 :(得分:1)

我遇到了与 Visual Studio 2017 .net MVC 5.2.4 类似的问题,正在更新Nuget Microsoft.Owin.Security.Google 到目前最新版本为 4.0.1 的最新版本对我有效! 希望这对某人有帮助!