JWT 验证失败
Posted
技术标签:
【中文标题】JWT 验证失败【英文标题】:JWT Validation fails 【发布时间】:2017-09-21 10:18:05 【问题描述】:我通过如下所示设置 Payload 创建了一个 JwtToken
var payload = new JwtPayload
"aud", "wellmark.com" ,
"iss", "wellmark" ,
"iat", DateTime.Now.Ticks ,
"exp", DateTime.Now.AddDays(90).Ticks ,
;
我必须使用刻度的原因是因为这是获取发布时间和到期时间的整数值的唯一方法。我同意 ticks 实际上是 long 而不是 int,但这是唯一的方法。
现在当我回来验证令牌时,我正在执行以下操作
var tokenValidationParams = new TokenValidationParameters
IssuerSigningKey = new X509SecurityKey(jwtCert),
ValidAudience = "masked",
ValidIssuer = "masked",
IssuerSigningKeyResolver = (string token, Microsoft.IdentityModel.Tokens.SecurityToken securityToken, string kid, TokenValidationParameters validationParameters) => new List<X509SecurityKey> new X509SecurityKey(jwtCert)
;
tokenHandler.ValidateToken(id, tokenValidationParams
, out validatedToken);
但是说不出来
生命周期验证失败。令牌缺少过期时间。 令牌类型:'System.IdentityModel.Tokens.Jwt.JwtSecurityToken'。
这很可能是因为验证方法正在尝试将 long 转换为 int,并且因为它无法转换它,所以它只是返回一个 null,如显示的文档中所示 here。
有没有人在这个机制上取得了成功。请注意,我使用 X509 证书来签署我的 Jwt。
纳拉辛哈姆
【问题讨论】:
【参考方案1】:另一点值得注意的是,虽然@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)
【讨论】:
【参考方案2】:你不能使用 Ticks 作为 exp 时间戳
JWT 中的时间戳是从 01.01.1970 00:00 UTC 开始计算的 UNIX 时间戳:https://www.rfc-editor.org/rfc/rfc7519#section-4.1.4 解释说数字日期用于 exp 声明(也用于 nbf(不是之前)和 iat(发布时间)声明)
https://www.rfc-editor.org/rfc/rfc7519#section-2 定义数字日期:
一个 JSON 数值,表示从 1970-01-01T00:00:00Z UTC 直到指定的 UTC 日期/时间,忽略 闰秒。
如果您像在示例中那样直接创建有效负载,则需要计算自 1.1.1970 UTC 以来的秒数,例如:
DateTime centuryBegin = new DateTime(1970, 1, 1);
var exp = new TimeSpan(DateTime.Now.AddDays(90).Ticks - centuryBegin.Ticks).TotalSeconds;
exp 是从现在开始的 90 天。当然,您也可以使用任何其他 Add... 方法来计算到期时间。 参考https://msdn.microsoft.com/de-de/library/system.datetimeoffset(v=vs.110).aspx查看其他的Add方法。
一些框架(例如 System.IdentityModel.Tokens.Jwt)提供了创建令牌的函数,这些令牌接受 DateTimeOffset 类型的参数,这更容易处理。
使用http://www.unixtimestamp.com/ 或类似网站检查您的时间戳
【讨论】:
以上是关于JWT 验证失败的主要内容,如果未能解决你的问题,请参考以下文章