每次用户登录 SaaS 应用时,系统都会生成一个
认证令牌
并下发到用户的浏览器或移动端。这个令牌内部藏着一个隐形的计时器。
JWT 过期时间
字段以普通 Unix 时间戳的形式存储,告诉所有读取它的服务器何时停止信任这份凭证。哪怕时间戳差了几秒,用户可能就会被提前踢出登录,或者更糟糕的是,会话有效期远超安全策略的允许范围。深入理解
JSON web token
的声明机制,尤其是
exp
和
iat
,是 SaaS 团队在不影响用户体验的前提下收紧身份验证的最实用手段之一。
核心要点:
-
JWT JSON web token
中的
exp和iat声明始终是以整数秒为单位的 Unix 时间戳,而不是毫秒。 - 服务器之间的时钟偏差可能悄悄导致令牌失效,或让会话超出策略规定的时长。业界标准的解决方案是设置 60 秒的容差窗口。
- 令牌有效期 设置过长会增加安全风险,设置过短则会破坏用户体验。使用一个明确的公式可以帮你找到合适的值。
- 你可以用 Unix 纪元转换工具直接解码并验证任意 JSON web token 中的时间戳(以秒为单位),无需引入任何库。
目录
什么是 JWT,为什么时间戳存在于其中
JSON web token 是一种紧凑、URL 安全的凭证格式,由三个 Base64 编码的部分组成,以点号分隔:header(头部)、payload(载荷)和 signature(签名)。payload 是存放时效性数据的地方。由于 JWT 是 无状态的 ,服务器不会查询数据库来验证会话,而是直接读取 payload 中内嵌的声明,并信任加密签名。这种设计性能好、扩展性强,但也带来了一个真实的约束:令牌一旦签发,服务器就无法"删除"它。限制令牌生命周期的唯一可靠方式,就是在令牌本身中直接嵌入一个过期时间戳。
这正是时间,具体来说是 Unix 纪元时间 ,成为 JWT 安全核心的原因。定义 JWT 的 RFC 7519 规范 要求所有与时间相关的声明都以"NumericDate"格式表示,即自 1970 年 1 月 1 日 UTC 零点起经过的秒数。没有时区,没有本地化格式,只有一个整数,世界上任何服务器都可以拿它与自己的时钟直接比较。
exp
和
iat
声明详解
JWT 规范定义了多个注册声明,其中对令牌生命周期管理最关键的是以下两个:
-
exp(过期时间): 令牌在此时间戳之后不得被接受。服务器必须拒绝当前时间大于或等于该值的令牌。这是 JWT 过期机制 的核心。 -
iat(签发时间): 令牌被创建时的时间戳。 iat 声明默认不强制校验,但非常实用。它可以让你计算令牌的"年龄",独立于exp之外执行最大有效期策略,还能检测出签发时间异常靠前的可疑令牌。
第三个声明
nbf
(Not Before)定义了令牌最早生效的时间,使用频率较低,但同样遵循 Unix 时间戳格式。
这三个值都是整数。如果你的系统用毫秒生成它们(在 JavaScript 中直接使用
Date.now()
是常见的错误),得到的数值会比预期大约 1000 倍。服务器在将
exp
与当前纪元秒数比较时,会看到一个"有效期到 33658 年"的令牌,永远不会拒绝它。这是一个真实存在的线上 bug,已经影响过多个 SaaS 团队。
实战示例:解码真实令牌时间戳
假设你的认证服务在用户于 2025 年 7 月 1 日 UTC 上午 10:00 登录后,签发了以下 JWT payload:
{
"sub": "user_8821",
"iat": 1751364000,
"exp": 1751367600,
"role": "admin"
}
下面来解读这些数字的含义:
| 声明 | Unix 时间戳 | 可读时间(UTC) | 含义 |
|---|---|---|---|
iat
|
1751364000 | 2025-07-01 10:00:00 | 令牌在此时刻签发 |
exp
|
1751367600 | 2025-07-01 11:00:00 | 令牌恰好在 1 小时后过期 |
两者相差恰好 3600 秒,即一小时。任何在 UTC 11:00:00 之后收到该令牌的服务器都必须拒绝它。手动验证方法是用
exp
减去
iat
:1751367600 - 1751364000 = 3600 秒。你可以用 Unix 时间戳转换工具即时确认这些值对应的可读时间,这正是避免前面提到的毫秒与秒混淆 bug 的快速健全性检查方式。
关于数据库应如何存储这些值,可以参考我们的指南
数据库中的 Unix 时间戳
,无论是将
exp
存入令牌还是审计日志表,使用的都是同一种整数格式。
时钟偏差:悄无声息的令牌杀手
这是一个经常让团队措手不及的真实问题。在分布式系统中,你的认证服务器和 API 服务器是两台不同的机器。它们通过 NTP 同步系统时钟,但永远无法做到完全一致。在云环境中,30 到 90 秒的时间差很常见。
设想这样一个场景:一个
exp = 1751367600
的令牌被 API 服务器校验,而该服务器的时钟显示为 1751367610,仅比过期时间晚了 10 秒。令牌被拒绝,用户收到 401 错误。从他们的角度来看,自己明明刚刚登录,应用就崩了。
标准的解决方案是在校验逻辑中加入 时钟偏差容差 。大多数 JWT 库都支持 leeway(容差)参数。60 秒是业界广泛接受的值,也被 OpenID Connect Core 规范 所引用。设置好这个值,写入文档,并确保你整个技术栈中的每个服务都使用相同的配置。
实用建议:
在
exp
校验中加入 60 秒的容差。但不要对
iat
的检查加容差,因为那样会允许接受签发时间在未来的令牌,这是重放攻击的危险信号。
为你的 SaaS 产品选择合适的令牌有效期
合适的 令牌有效期 取决于你的应用敏感程度和预期的会话行为。以下是一个实用参考框架:
- 高敏感度应用(银行、医疗、管理后台): 访问令牌有效期 15 到 30 分钟,配合刷新令牌静默续期会话。
- 标准 SaaS 应用(项目管理、CRM、数据分析): 1 到 8 小时,与典型工作会话时长匹配。
- 低风险或只读 API: 最长 24 小时,但对敏感操作应配合令牌轮换机制。
- 机器间服务令牌: 可以更长(数天或数周),但应严格限制权限范围,并按计划轮换。
签发时计算
exp
值的实用公式:
// Node.js 示例
const iat = Math.floor(Date.now() / 1000); // 当前时间,单位:秒
const ttl = 3600; // 存活时间:1 小时,单位:秒
const exp = iat + ttl;
const payload = {
sub: userId,
iat: iat,
exp: exp
};
注意这里显式除以 1000,将毫秒转换为秒。就是这一行代码,避免了 JavaScript 应用中最常见的 JWT 时间戳 bug。
落地 JWT 过期机制的具体步骤
以下是一份可以立即应用于任何 SaaS 认证系统的具体检查清单:
-
始终同时包含
iat和exp。iat声明在技术上是可选的,但它提供了审计追踪能力,并支持独立于过期时间之外的最大有效期策略。 -
部署前验证时间戳格式。
解码你的令牌,确认
exp是一个 10 位整数。如果是 13 位,说明毫秒混入了。 - 在校验 middleware 中设置 60 秒的时钟偏差容差。 确保所有消费认证令牌的服务都统一应用这一配置。
- 使用短期访问令牌配合刷新令牌。 15 分钟的访问令牌加上 7 天的刷新令牌,远比单一的 7 天访问令牌安全,因为刷新令牌可以在服务端撤销。
-
在每次认证请求中记录
iat值。 这有助于发现异常情况,例如同一个令牌同时从两个不同地区被使用。 -
在预发布环境中用加速时间测试过期逻辑。
模拟时钟跳过
exp值,确认应用能优雅地处理 401 响应,而不是崩溃或陷入循环。
如果你需要快速将令牌中的原始
exp
值转换为可读日期,或者验证某个时长对应的时间戳应该设置为多少,
我们首页的纪元转换工具
可以即时处理两个方向的转换,无需任何代码。
总结
JWT 过期机制本身并不复杂,但这里的小失误往往会造成大的安全漏洞。
exp
和
iat
声明只是整数,是从纪元起点开始计算的 Unix 时间戳,单位为秒。这种简洁性正是它们的优势:任何服务器、任何语言、任何平台都能无歧义地比较两个数字。真正的挑战在于正确地运用它们:选择合理的
令牌有效期
时长,用容差窗口处理时钟偏差,并在上线前发现毫秒与秒混淆的问题。将这些习惯融入你的认证流水线,你的
JSON web token
实现就能同时兼顾安全性和可维护性。
即时验证 JWT 时间戳,无需编写代码
将 JWT payload 中的任意 Unix 时间戳粘贴进来,一键转换为可读日期。在上线前发现毫秒与秒混淆的错误。
立即试用免费工具 →
exp
(过期时间)是令牌失效的 Unix 时间戳,超过该时间戳后令牌必须被拒绝。
iat
(签发时间)是令牌被创建时的时间戳。两者都是以秒为单位的整数。
iat
声明是可选的,但对审计和执行最大令牌有效期策略非常有用。
Unix 时间戳是与时区无关的整数,在所有服务器和地区之间都没有歧义。"2025-07-01T10:00:00"这样的格式化日期字符串可能因本地化或时区设置不同而被误解。在高吞吐量的认证系统中,整数比较也比字符串解析更快、更不容易出错。
长期有效的令牌即使在用户账号被盗或权限发生变更后依然有效。由于 JWT 是无状态的,服务器无法在令牌有效期内将其撤销。如果攻击者窃取了一个有效期为 30 天的令牌,就拥有了长达 30 天的访问权限。将短期访问令牌与刷新令牌结合使用,可以大幅缩小这个风险窗口。
在 JavaScript 中,生成
iat
或
exp
值时始终使用
Math.floor(Date.now() / 1000)
。原始的
Date.now()
返回的是毫秒值,除以 1000 才能转换为 JWT 规范要求的秒级整数。验证方法是检查你的时间戳是否为 10 位数字,而不是 13 位。
时钟偏差是分布式系统中两台服务器系统时钟之间的微小时间差。即使只有 30 秒的差异,也可能导致校验服务器的时钟略微超前,从而拒绝一个本应有效的令牌。标准的解决方案是在 JWT 校验库中配置 60 秒的容差,以吸收正常的 NTP 漂移。