另一点值得注意的是,虽然@jps 的回答实际上在技术上是正确的,但它并不能解释为什么你得到了这个例外:Lifetime validation failed. The token is missing an Expiration Time. Tokentype: 'System.IdentityModel.Tokens.Jwt.JwtSecurityToken';为什么它会暗示你错过了一个过期时间,即使你已经清楚地提供了一个 Int64(长)?理论上,任何 Int64 数字都应该代表未来的某个时间点,即使考虑到从 January 1st 1970 (https://www.rfc-editor.org/rfc/rfc7519#section-2) 开始的 epcoh 时间限制。
真正的根本问题是,您在使用 Ticks 时使用的数字是一个非常大的数字。一方面,微软将其定义为:
自 0001 年 1 月 1 日午夜 12:00:00(公历 0001 年 1 月 1 日 0:00:00 UTC,0001 年 1 月 1 日 UTC)以来经过的 100 纳秒间隔数。https://msdn.microsoft.com/en-us/library/system.datetime.ticks(v=vs.110).aspx
上面有两点要记住:
- 一直追溯到
January 1, 0001
- 它基于纳秒,纪元时间基于毫秒(1000000 倍)。
综上所述,这在理论上应该仍代表未来的某个时间点,尽管在超远的未来。
那么为什么它不工作 - 什么时候真的应该!?
您实际上最终得到 SO 票证中提到的异常的根本原因实际上是不是您使用的是 Ticks(而不是纪元时间戳),而是因为微软有一个理论上的使用 JWT 的框架和 Owin 中间件时验证 JWT 的上限(稍后会详细介绍,请继续阅读)。
希望:
-
这样的上限真的不应该强加;但也许有一些安全用例需要对票的到期日期施加这样的上限——我现在真的想不出。
-
尽管有上面的第 1 条,人们还是希望上述上限在未来真的很遥远(如果您既是millennial 阅读此文并且听说过/知道 Y2K 错误,则可以获得额外的积分奖励积分?) .然而 MS 团队设定的限制实际上指日可待:January 19, 2038 @ 03:14:07 GMT
您可以通过尝试使用设置为 2038 年 1 月 20 日 (2147558400) 的 exp 值来验证以下令牌来自己测试它。
{
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role": "SampleToken",
"nbf": 1507578537,
"exp": 2147558400, //<== This timestamp will not work :/
"iss": "https://api.example.com",
"aud": "https://example.com"
}
验证失败
Microsoft.IdentityModel.Tokens.SecurityTokenException 和异常消息:IDX10225:生命周期验证失败。令牌缺少过期时间
这不是真的,我提供了一个过期日期,它是一个有效的纪元时间戳(而不是一个由 DateTime.Ticks 表示的非常大的数字;)。
如果您重复相同的测试,通过尝试使用设置为 2038 年 1 月 19 日 00:00:00 (2147472000) 的 exp 值来验证以下令牌 - 这恰好在当前 MSFT 的理论限制之前January 19, 2038 @ 03:14:07 GMT 的库,它可以工作。
{
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role": "SampleToken",
"nbf": 1507578537,
"exp": 2147472000,
"iss": "https://api.example.com",
"aud": "https://example.com"
}
结论:
- Microsoft 需要:
一个。延长令牌到期值的最大日期
b.或者,为 exp 值超过当前最大值的情况添加另一条异常消息 - 可能是 IDX10226: Liftetime validation not possible. Expiration date set too far in the future。
奖励积分
好的,在详细说明了exp 声明的上限之后,如果不解释为什么一开始就有上限,那就太可惜了。一些阅读这篇文章的人已经发现了上限的原因,因为我已经在整个文章中丢掉了线索;如果您已经掌握了它,向您致敬,如果您还没有,原因如下:
在比较“exp”声明的 exp 声明纪元时间戳的值时,Microsoft 隐式使用整数或 (Int32)。我想将此视为疏忽(有人可能没有充分考虑架构,也可能没有考虑使用(显式或隐式)Int32 与长数据类型(Int64)来存储/比较的含义纪元时间戳。让我感到愚蠢的是,选择 Int32 作为数据类型来与他们的库的纪元时间戳交互是一个实际的架构决定——但话又说回来,就像我在这篇文章前面所说的那样,也许有安全性需要这种强加上限的用例(我现在想不出一个)。
仍然不相信,这是 Int32 可以容纳的最大值:2147483647,根据 Microsoft:https://msdn.microsoft.com/en-us/library/system.int32.maxvalue(v=vs.110).aspx。假设 2147483647 是一个纪元时间戳,猜猜将 2147483647 转换为日期时得到的 GMT 日期/时间?是的,您确实得到:格林威治标准时间 2038 年 1 月 19 日 03:14:07。您可以使用https://www.epochconverter.com 方便转换。
解决方法
-
最明显的一个是不要创建在纪元时间戳 2147483647 之后的任何时间点过期的令牌。在撰写本文时,微软的 owin 中间件无法对其进行验证(希望他们尽快解决这个问题)。
-
另一种解决方法是编写自己的令牌生命周期验证逻辑。它并不像人们想象的那么复杂,但它确实有助于理解底层的 Owin 中间件和 JW*(JWT、JWE、JWS、JWK 等。Michael B. Jones 有一篇关于JW* 在这里:http://www.niso.org/apps/group_public/download.php/14003/SP_Jones_JSON_isqv26no3.pdf)