导致 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.ExpirationDateTime.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,但在这种特殊情况下,没关系。