我们很惊讶,于是开始寻找原因。我们首先检查了 Contoso.com 的源代码,让我们感到欣慰的是,问题不在那。接着,为了确保问题与应用程序宿主在 Web 领域无关,我们只保留一个服务器在运行,而关闭了所有其他服务器。问题仍然存在,这并不意外,因为我们的日志显示匹配的 Set-Cookie 标头绝不会来自两个不同的服务器。ASP.NET 意外地生成了重复的会话 ID,这令人难以置信,因为它使用 .NET Framework RNGCryptoServiceProvider 类生成这些 ID,并且会话 ID 的长度足以确保相同的 ID 决不会生成两次(至少在下一个万亿年内不会生成两次)。除此之外,即使 RNGCryptoServiceProvider 错误地生成了重复的随机数字,也无法解释 ASP.NET 为何不可思议地将有效的会话 ID 替换为新的 ID(不唯一)。
凭直觉,我们决定看一下输出缓存。当 OutputCacheModule 缓存 HTTP 响应时,它必须小心不要缓存了 Set-Cookie 标头;否则,包含新会话 ID 的缓存响应会将缓存响应的所有接收者(以及其请求生成了缓存响应的用户)连接到同一会话。我们检查了源代码;Contoso.com 在两个页面中启用了输出缓存。我们关闭了输出缓存。结果,应用程序运行数天而没有发生一个跨会话问题。此后,它运行了两年多都没有发生任何错误。在具有不同应用程序和一组不同 Web 服务器的另一家公司中,我们看到完全相同的问题也消失了。就像在 Contoso.com 一样,消除输出缓存就能解决问题。
Microsoft 后来确认此行为源于 OutputCacheModule 中的问题。(当您阅读本文时,可能已经发布了更新。)当 ASP.NET 与 IIS 6.0 一起使用并且启用内核模式缓存时,OutputCacheModule 有时无法从它传递给 Http.sys 的缓存响应中删除 Set-Cookie 标头。下面是导致出现错误的特定事件顺序:
• 最近没有访问网站(因此也没有对应的会话)的用户请求一个启用了输出缓存的页面,但是其输出当前在缓存中不可用。
• 该请求执行用于访问用户最新创建的会话的代码,从而导致会话 ID Cookie 在响应的 Set-Cookie 标头中返回。
• OutputCacheModule 向 Http.sys 提供输出,但是无法从响应中删除 Set-Cookie 标头。
• Http.sys 在后续的请求中返回缓存响应,误将其他用户连接到会话。
故事的寓意又是什么呢?会话状态和内核模式输出缓存不能混合使用。如果您在启用输出缓存的页中使用会话状态,并且应用程序在 IIS 6.0 上运行,则您需要关闭内核模式输出缓存。您仍将受益于输出缓存,但是因为内核模式输出缓存比普通输出缓存快得多,所以缓存不会同样有效。有关此问题的详细信息,请参见 support.microsoft.com/kb/917072。
您可以通过在页面的 OutputCache 指令中包含 VaryByParam="*" 属性来关闭单个页面的内核模式输出缓存,虽然这样做可能导致内存需求骤增。另一种更安全的方法是通过在 web.config 中包含下列元素来关闭整个应用程序的内核模式缓存:
<httpRuntime enableKernelOutputCache="false" />
您还可以使用注册表设置来全局性地禁用内核模式输出缓存,即禁用全部服务器的内核模式输出缓存。有关详细信息,请参见 support.microsoft.com/kb/820129。
每次我听到客户报告会话发生了费解的问题,我都会询问他们是否在任何页面中使用了输出缓存。如果确实使用了输出缓存,并且宿主操作系统是 Windows Server 2003,我会建议他们禁用内核模式输出缓存。问题通常就会迎刃而解。如果问题没有解决,则错误存在于代码中。警惕!
返回页首
Forms 身份验证票证生存期
您能找出以下代码的问题吗?
FormsAuthentication.RedirectFromLoginPage(username, true);
此代码看似没有问题,但决不能在 ASP.NET 1.x 应用程序中使用,除非应用程序中其他位置的代码抵消了此语句的负面作用。如果您不能确定原因,请继续阅读。
FormsAuthentication.RedirectFromLoginPage 执行两个任务。首先,当 FormsAuthenticationModule 将用户重定向到登录页时,FormsAuthentication.RedirectFromLoginPage 将用户重定向到他们原来请求的页面。其次,它发布一个身份验证票证(通常携带在 Cookie 中,而且在 ASP.NET 1.x 中总是携带在 Cookie 中),这个票证允许用户在预定的一段时间内保持已经过身份验证状态。
问题就在于这个时间段。在 ASP.NET 1.x 中,向 RedirectFromLoginPage 传递另一个为 false 的参数会发出一个临时身份验证票证,该票证默认情况下在 30 分钟之后到期。(您可以使用 web.config 的 元素中的 Timeout 属性来更改超时期限。)然而,传递另一个为 true 的参数则会发出一个永久身份验证票证,其有效期为 50 年!这样就会发生问题,因为如果有人窃取了该身份验证票证,他们就可以在票证的有效期内使用受害者的身份访问网站。窃取身份验证票证有多种方法 — 在公共无线访问点探测未加密的通信、跨网站编写脚本、以物理方式访问受害者的计算机等等 — 因此,向 RedirectFromLoginPage 传递 true 比禁用您的网站的安全性好不了多少。幸运的是,此问题已经在 ASP.NET 2.0 中得到了解决。现在的 RedirectFromLoginPage 以相同的方式接受在 web.config 中为临时和永久身份验证票证指定的超时。
一种解决方案是决不在 ASP.NET 1.x 应用程序的 RedirectFromLoginPage 的第二个参数中传递 true。但是这不切实际,因为登录页的特点通常是包含一个“将我保持为登录状态”框,用户可以选中该框以收到永久而不是临时身份验证 Cookie。另一种解决方案是使用 Global.asax(如果您愿意的话,也可以使用 HTTP 模块)中的代码段,此代码段会在包含永久身份验证票证的 Cookie 返回浏览器之前对其进行修改。
图 3 包含一个这样的代码段。如果此代码段位于 Global.asax 中,它会修改传出永久 Forms 身份验证 Cookie 的 Expires 属性,以使 Cookie 在 24 小时后过期。通过修改注释为“新的过期日期”的行,您可以将超时设置为您喜欢的任何日期。
您可能会觉得奇怪,Application_EndRequest 方法调用本地 Helper 方法 (GetCookieFromResponse) 来检查身份验证 Cookie 的传出响应。Helper 方法是解决 ASP.NET 1.1 中另一个错误的方法,如果您使用 HttpCookieCollection 的字符串索引生成器来检查不存在的 Cookie,此错误会导致虚假 Cookie 添加到响应中。使用整数索引生成器作为 GetCookieFromResponse 可以解决该问题。
文章整理:西部数码--专业提供域名注册、虚拟主机服务
http://www.west263.com
以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢!



