导致 DST 结束的奇怪表单身份验证过期行为
Strange Forms Authentication Expiration behaviour leading up to DST End
我有一个使用表单身份验证的 ASP.NET Web 应用程序。我们在 10 月 25 日凌晨 1 点到凌晨 2 点之间(在英国,夏令时在 2015 年 10 月 25 日凌晨 2 点结束,时钟倒退一小时)在我们的实时网站上目睹了一些奇怪的行为,所有用户都注销了身份验证票通过以下代码过期:
protected void Application_BeginRequest(object sender, EventArgs e)
{
HttpCookie cookie = Request.Cookies["MyCookie"];
FormsAuthenticationTicket ticket;
try
{
ticket = FormsAuthentication.Decrypt(cookie.Value);
}
catch
{
this.RedirectDueToError(ErrorView);
return;
}
if (ticket == null) { return; }
if (ticket.Expiration < DateTime.Now)
{
this.RedirectDueToError(TimeoutView);
}
}
Web.config 包含:
<authentication mode="Forms">
<forms name="MyCookie" timeout="40" requireSSL="false"/>
</authentication>
我在测试服务器上重现了该问题,可以看到问题发生在凌晨 1 点到凌晨 2 点 夏令时结束之间。当时间到达凌晨 2 点并自动变回一个小时到凌晨 1 点时,问题就解决了。
在我的测试过程中,我添加了一些额外的日志记录以确定失败时 ticket.Expiration
和 DateTime.Now
的值(这也是在最初创建身份验证票证之后)。结果是:
夏令时结束前:
服务器时间:00:53:39
DateTime.Now
= 00:53:39
ticket.Expiration
= 01:33:37(正确)
夏令时结束前:
服务器时间:01:25:08
DateTime.Now
= 01:25:08
ticket.Expiration
= 01:05:08(错误)
Post夏令时结束:
服务器时间:01:35:06
DateTime.Now
= 01:35:06
ticket.Expiration
= 02:15:06(正确)
所以看起来在 DST 结束前的一个小时,从 FormsAuthenticationTicket
Expiration
属性 返回的时间晚了一个小时。我知道到期时间在内部存储为 UTC,但 Expiration
属性 是当地时间,我怀疑转换有问题。
知道为什么会这样吗?代码问题?服务器问题?
问题的症结就在这里:
if (ticket.Expiration < DateTime.Now)
将其更改为:
if (ticket.Expiration.ToUniversalTime() < DateTime.UtcNow)
或者更好的是,对于这个,它在内部做同样的事情:
if (ticket.Expired)
问题其实很详细in the comments found in the MSDN reference sources。
基本上归结为DateTime
结构的设计。两个 DateTime
对象的比较仅考虑其 Ticks
值,而不考虑其 Kind
,因此不考虑 DST 或时区。
此外,我通常不建议使用 ToUniversalTime
,但在这种特殊情况下,没关系。
我有一个使用表单身份验证的 ASP.NET Web 应用程序。我们在 10 月 25 日凌晨 1 点到凌晨 2 点之间(在英国,夏令时在 2015 年 10 月 25 日凌晨 2 点结束,时钟倒退一小时)在我们的实时网站上目睹了一些奇怪的行为,所有用户都注销了身份验证票通过以下代码过期:
protected void Application_BeginRequest(object sender, EventArgs e)
{
HttpCookie cookie = Request.Cookies["MyCookie"];
FormsAuthenticationTicket ticket;
try
{
ticket = FormsAuthentication.Decrypt(cookie.Value);
}
catch
{
this.RedirectDueToError(ErrorView);
return;
}
if (ticket == null) { return; }
if (ticket.Expiration < DateTime.Now)
{
this.RedirectDueToError(TimeoutView);
}
}
Web.config 包含:
<authentication mode="Forms">
<forms name="MyCookie" timeout="40" requireSSL="false"/>
</authentication>
我在测试服务器上重现了该问题,可以看到问题发生在凌晨 1 点到凌晨 2 点 夏令时结束之间。当时间到达凌晨 2 点并自动变回一个小时到凌晨 1 点时,问题就解决了。
在我的测试过程中,我添加了一些额外的日志记录以确定失败时 ticket.Expiration
和 DateTime.Now
的值(这也是在最初创建身份验证票证之后)。结果是:
夏令时结束前:
服务器时间:00:53:39
DateTime.Now
= 00:53:39
ticket.Expiration
= 01:33:37(正确)
夏令时结束前:
服务器时间:01:25:08
DateTime.Now
= 01:25:08
ticket.Expiration
= 01:05:08(错误)
Post夏令时结束:
服务器时间:01:35:06
DateTime.Now
= 01:35:06
ticket.Expiration
= 02:15:06(正确)
所以看起来在 DST 结束前的一个小时,从 FormsAuthenticationTicket
Expiration
属性 返回的时间晚了一个小时。我知道到期时间在内部存储为 UTC,但 Expiration
属性 是当地时间,我怀疑转换有问题。
知道为什么会这样吗?代码问题?服务器问题?
问题的症结就在这里:
if (ticket.Expiration < DateTime.Now)
将其更改为:
if (ticket.Expiration.ToUniversalTime() < DateTime.UtcNow)
或者更好的是,对于这个,它在内部做同样的事情:
if (ticket.Expired)
问题其实很详细in the comments found in the MSDN reference sources。
基本上归结为DateTime
结构的设计。两个 DateTime
对象的比较仅考虑其 Ticks
值,而不考虑其 Kind
,因此不考虑 DST 或时区。
此外,我通常不建议使用 ToUniversalTime
,但在这种特殊情况下,没关系。