首页 文章

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

提问于
浏览
126

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

And within my login procedure I have the following code:

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

Update 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上工作的所有其他浏览器有同样的问题 .

Update 2: 在使用OWIN进行身份验证之前,首先在我的login_load页面上强制创建 ASPNET_SessionId .

1)在我使用OWIN进行身份验证之前,我在登录页面上创建一个随机 Session.Content 值以启动ASP.NET_SessionId 2)然后我进行身份验证并进行进一步的会话3)其他浏览器似乎现在正在工作

这很奇怪 . 我只能得出结论,这与ASP和OWIN有关,认为它们处于不同的域或类似的东西 .

Update 3 - 两者之间的奇怪行为 .

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

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

Update 3B

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

Summary of Workarounds

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

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

8 回答

  • 14

    我遇到了同样的问题,并追溯到OWIN ASP.NET托管实现的原因 . 我会说这是一个错误 .

    Some background

    我的发现基于这些装配版本:

    • 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及其 Headers 集合 . 因此,当通过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的静态属性实现,这是非常自我解释的 . 如果您在应用程序中存储了会话状态,则此模块将执行此操作为每个请求多做一点工作 .


    Back to our login problem

    通过所有这些部分,您可以解释您的场景 .

    Case 1 - Session was never set

    System.Web.SessionState.SessionStateModule,s_sessionEverSet属性为false . 会话状态模块没有生成会话ID,System.Web.HttpResponse.Cookies集合状态为 not detected as changed . 在这种情况下,OWIN cookie正确发送到浏览器并登录工作 .

    Case 2 - Session was used somewhere in application, but not before user tries to authenticate

    System.Web.SessionState.SessionStateModule,s_sessionEverSet属性为true . 会话ID由SessionStateModule生成,ASP.NET_SessionId被添加到System.Web.HttpResponse.Cookies集合但它's removed later in request lifetime as user'的会话实际上是空的 . 在这种情况下,System.Web.HttpResponse.Cookies集合状态为 detected as changed ,并且在将cookie序列化为标头值之前首先清除Set-Cookie标头 .

    在这种情况下,OWIN响应cookie“丢失”,用户未经过身份验证,并被重定向回登录页面 .

    Case 3 - Session is used before user tries to authenticate

    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 header is NOT first cleared 中的内部优化,但仅更新 .

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


    More general problem with cookies

    正如您所看到的,问题更为一般,并不仅限于ASP.NET会话 . 如果您通过Microsoft.Owin.Host.SystemWeb托管OWIN,并且您/某些东西直接使用System.Web.HttpResponse.Cookies集合,则您将面临风险 .

    例如 this works 并且两个cookie都正确发送到浏览器...

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

    但是 this does not 和OwinCookie是"lost" ......

    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项目模板测试 .

  • 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());
    
  • 142

    简而言之, the .NET cookie manager will win over the OWIN cookie manager and overwrite cookies set on the OWIN layer . 修复是使用SystemWebCookieManager class, provided as a solution on the Katana Project here . 你需要使用这个类或类似的类,它将 force OWIN to use the .NET cookie manager so there are no inconsistencies

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

    A similar answer has been provided here but it does not include all of the code-base required to solve the problem, so I see a need to add it here because the external link to the Katana Project may go down and this should be fully chronicled as a solution here as well.

  • 3

    Katana团队回复了issue Tomas Dolezar,并发布了documentation about workarounds

    变通办法分为两类 . 一种是重新配置System.Web,以避免使用Response.Cookies集合并覆盖OWIN cookie . 另一种方法是重新配置受影响的OWIN组件,以便将cookie直接写入System.Web的Response.Cookies集合 . 确保在身份验证之前 Build 会话:System.Web和Katana Cookie之间的冲突是按请求进行的,因此应用程序可以在身份验证流程之前根据某些请求 Build 会话 . 当用户第一次到达时,这应该很容易做到,但是稍后当会话或授权cookie到期和/或需要刷新时可能更难保证 . 禁用SessionStateModule - 如果应用程序不依赖会话信息,但会话模块仍在设置导致上述冲突的cookie,则可以考虑禁用会话状态模块 . 重新配置CookieAuthenticationMiddleware以直接写入System.Web的cookie集合 .

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

    请参阅文档中的SystemWebCookieManager实现(上面的链接)

    更多信息here

    Edit

    下面我们采取的步骤来解决问题 . 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 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),
                });
        }
    }
    
  • 1

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

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

    最快的单行代码解决方案:

    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);
    
  • 25

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

    事实证明,它是使用自定义 CookieAuthenticationMiddleware 与WebApi以及WebApi compression support的组合

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

    System.Web.HttpException发送HTTP标头后,服务器无法附加标头 .

    这导致我这个GitHub Issue

    基本上,如果您有像我这样的奇怪设置,您将需要disable compression用于设置cookie的WebApi控制器/方法,或尝试 OwinServerCompressionHandler .

相关问题