NET _ SessionId + OWIN Cookies 不会发送到浏览器

对于使用 Owin cookie 身份验证,我有一个奇怪的问题。

当我开始我的 IIS 服务器验证在 IE/Firefox 和 Chrome 上运行的非常好。

我开始使用 Authentication 进行一些测试,并在不同的平台上登录,我发现了一个奇怪的错误。偶尔 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. content 的页面..。 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)我有一个基于 cookie 的授权会话 我设置了一些会话变量

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

更新3B

深入研究之后,我在一个页面上看到一些评论,说“ Forms”身份验证超时和会话超时需要匹配。我认为通常情况下,两者是同步的,但不管出于什么原因,两者并不同步。

工作区摘要

1)在验证之前总是先创建会话。基本上是在启动应用程序 Session["Workaround"] = 0;时创建会话

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

93845 次浏览

我曾经遇到过同样的问题,并且追踪原因到 OWIN ASP.NET 托管实现。我觉得是虫子。

一些背景资料

我的发现是基于这些汇编版本:

  • 微软。欧文,版本 = 2.0.2.0,文化 = 中立,PublicKeyToken = 31bf3856ad364e35
  • Microsoft. Owin. Host. SystemWeb,版本 = 2.0.2.0,文化 = 中性,PublicKeyToken = 31bf3856ad364e35
  • System. Web,Version = 4.0.0.0,Culture = Central,PublicKeyToken = b03f5f7f11d50a3a

OWIN 使用它自己的抽象来处理响应 Cookie (微软。欧文。响应 CookieCollection)。这个实现直接包装响应头集合,并相应地更新 设置-饼干头。OWIN ASP.NET 主机(微软。欧文。主机。系统网)刚刚包装了 System.Web.HttpResponse和它的头部集合。因此,当通过 OWIN 创建新 cookie 时,响应 设置-饼干标头将直接更改。

但是 ASP.NET 也使用它自己的抽象来处理响应 Cookie。这是作为 System.Web.HttpResponse. Cookies属性向我们公开的,并由密封类 System.Web.HttpCookieCollection实现。这个实现并不直接包装响应 设置-饼干报头,而是使用一些优化和少量内部通知来显示其状态更改为响应对象。

然后在请求生命周期的后期有一个点,测试 HttpCookieCollection更改后的状态(System.Web.HttpResponse. GenerateResponseHeadersForCookies ()) ,并将 cookie 序列化为 设置-饼干报头。如果此集合处于某种特定状态,则首先清除整个 Set-Cookie 标头,并从存储在集合中的 Cookie 中重新创建标头。

NET 会话实现使用 System.Web.HttpResponse. Cookies属性来存储它的 ASP.NET _ SessionId cookie。在 ASP.NET 会话状态模块(SessionStateModule)中也有一些基本的优化,它是通过一个名为 s _ sessionEverSet 的静态属性来实现的。如果在应用程序中存储会话状态的内容,这个模块将为每个请求做更多的工作。


回到我们的登录问题

有了这些片段,你的情景就可以得到解释。

案例1-会话从未设置

系统。韦伯。会议状态。SessionStateModule ,s _ sessionEverSet 属性为 false。会话状态模块不生成会话 ID,System.Web.HttpResponse. Cookies收集状态为 未检测到更改。在这种情况下 OWIN Cookie 被正确地发送到浏览器和登录工作。

Case 2-Session 在应用程序中的某个地方使用,但在用户尝试进行身份验证之前不会使用

系统。韦伯。会议状态。SessionStateModule ,s _ sessionEverSet 属性为 true。会话 Id 是由 会话状态模块生成的,ASP.NET _ SessionId 被添加到 System.Web.HttpResponse. Cookies集合中,但是在请求生命周期的后期被删除,因为用户的会话实际上是空的。在这种情况下,System.Web.HttpResponse. Cookies集合状态为 检测到变化,在将 Cookie 序列化为标头值之前,首先清除 设置-饼干标头。

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

Case 3-Session 在用户尝试进行身份验证之前使用

系统。韦伯。会议状态。SessionStateModule ,s _ sessionEverSet 属性为 true。会话 ID 由 会话状态模块生成,ASP.NET _ SessionId 添加到 System.Web.HttpResponse. Cookies。由于内部优化在 System.Web.HttpCookieCollectionSystem.Web.HttpResponse. GenerateResponseHeadersForCookies () Set-Cookie 头不是首先清除的但只更新。

在这种情况下,OWIN 身份验证 Cookie 和 ASP.NET _ SessionId Cookie 都会在响应和登录时发送。


Cookie 更普遍的问题

正如您所看到的,这个问题更为普遍,而且不仅限于 ASP.NET 会话。如果您通过 微软。欧文。主机。系统网托管 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 项目模板。

从@TomasDolezal 的伟大分析开始,我查看了 Owin 和 System.Web 源代码。

问题是这个系统。Web 有自己的 cookie 信息主源,而这不是 Set-Cookie 头。Owin 只知道 Set-Cookie 头部。一个变通方法是确保由 Owin 设置的任何 cookie 也在 HttpContext.Current.Response.Cookies集合中设置。

我已经制作了一个小型中间件(来源Nuget) ,它完全做到了这一点,目的是将其放置在 cookie 中间件注册之上。

app.UseKentorOwinCookieSaver();


app.UseCookieAuthentication(new CookieAuthenticationOptions());

武士刀队回应了托马斯 · 多尔扎提出的 问题,并发布了 关于变通方法的文档:

工作区分为两类: 一类是重新配置 避免使用 Response.Cookies 集合和 另一种方法是重新配置 受影响的 OWIN 组件,所以他们写曲奇直接到 System.Web 的 Response. Cookies 集合。

  • 确保在身份验证之前建立会话: System 之间的冲突。网页和武士刀饼干是每个请求,所以它可能是 应用程序可以根据某些请求建立会话 这应该很容易做到 用户首先到达,但是当 会话或认证 cookie 过期和/或需要刷新。
  • 禁用 SessionStateModule-如果应用程序不依赖会话信息,但会话模块仍然设置 如果 cookie 导致上述冲突,则可以考虑禁用 会话状态模块。
  • 重新配置 CookieAuthenticationMiddleware 以直接写入 System.Web 的 Cookie 集合。
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
// ...
CookieManager = new SystemWebCookieManager()
});

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

更多信息 给你

剪辑

下面是我们解决这个问题的步骤。都是1。二。我们也分别解决了这个问题,但为了以防万一,我们决定同时使用这两种方法:

1. 使用 < a href = “ http://katanaproject.co落 x.com/wikipage? title = System.Web% 20response% 20cookie% 20Integration% 20issues & amp; ReferringTitle = Documents”rel = “ noReferrer”> SystemWebCookieManager

2. 设置 session 变量:

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。初始化使会话可用。但是,这个修复也可以在以后应用,因为在 OpenId 中首先有一个匿名请求,然后重定向到 OpenId 提供程序,然后返回到应用程序。当修复程序在第一次匿名请求期间设置会话变量时,问题会在重定向回到应用程序之后发生,从而在任何重定向回发生之前修复问题)

编辑2

武士刀计划复制粘贴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),
});
}
}

简而言之,NET Cookie 管理器将赢得 OWIN Cookie 管理器并覆盖 OWIN 层上的 Cookie 集。修复方法是使用 SystemWebCookieManager 类,作为 Katana 项目的解决方案在此提供。你需要使用这个类或类似的类,它将 强制 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()
...
});

这里提供了一个类似的答案,但它没有包括解决这个问题所需的所有代码库,所以我认为有必要在这里添加它,因为到武士刀项目的外部链接可能会下降,这应该作为一个解决方案在这里完全记录。

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

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

结果证明,它是使用自定义 CookieAuthenticationMiddleware和 WebApi 以及 支持 WebApi 压缩的组合

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

System.Web.HttpException 服务器不能在 HTTP 后追加标头 标题已经发送。

所以我找到了这个 GitHub 问题

基本上,如果您有一个像我这样奇怪的设置,您将希望为您的设置 cookie 的 WebApi 控制器/方法使用 停止压缩,或者尝试使用 OwinServerCompressionHandler

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

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

答案已经提供,但是在 owin3.1.0中,有一个 SystemWebChunkingCookieManager 类可以使用。

Https://github.com/aspnet/aspnetkatana/blob/dev/src/microsoft。欧文。主机。系统网/ systemwebchunkingcookiemanager.cs

Https://raw.githubusercontent.com/aspnet/aspnetkatana/c33569969e79afd9fb4ec2d6bdff877e376821b2/src/microsoft 主机系统网/ systemwebchunkingcookiemanager.cs

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

我面临类似的问题与 Visual Studio 2017.net MVC 5.2.4,更新 Nuget 微软,欧文,安全,谷歌到最新的版本,目前是 4.0.1为我工作! 希望这对谁有帮助!