必须用 datetime.utcnow 而非 datetime.now,因 jwt 规范要求 exp/iat/nbf 等时间字段为 utc 时间戳,jwtsecuritytokenhandler 校验时强制按 utc 比较,否则时区偏差会导致提前过期或校验失败。

用 System.IdentityModel.Tokens.Jwt 生成带过期时间的 JWT Token
直接用 JwtSecurityTokenHandler 创建 token 是最稳妥的方式,它原生支持 expires、issuedAt、notBefore 等标准声明,且自动校验时间戳逻辑。
常见错误是手动拼 JSON 或用 DateTime.Now 而不转成 UTC —— 这会导致服务端校验失败,尤其部署在不同时区服务器时。
-
Expires必须传DateTime.UtcNow.AddMinutes(30)这类 UTC 时间,不能用DateTime.Now - 签名密钥要用
SymmetricSecurityKey包裹 byte[],别直接传字符串 - 算法固定选
SecurityAlgorithms.HmacSha256,别写错成HMACSHA256(大小写敏感)
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim("uid", "123") }),
Expires = DateTime.UtcNow.AddMinutes(30),
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your-32-byte-secret-key")),
SecurityAlgorithms.HmacSha256)
};
var token = new JwtSecurityTokenHandler().CreateToken(tokenDescriptor);
为什么不用 DateTime.Now 而必须用 DateTime.UtcNow
JWT 规范要求所有时间字段(exp、iat、nbf)以 Unix 时间戳(秒级整数)表示,且隐含为 UTC。.NET 的 JwtSecurityTokenHandler 在验证时会强制用 UTC 比较;如果生成时用了本地时间,实际过期窗口可能偏差几小时。
比如你在北京(UTC+8),DateTime.Now.AddMinutes(30) 是 10:30,但转成 Unix 时间戳后,校验逻辑会把它当 UTC 时间解读,相当于提前 8 小时过期。
- 本地开发环境时间设置为非 UTC 时,问题立刻暴露
- Docker 容器默认时区常为 UTC,但宿主机可能是 CST,混合部署极易踩坑
-
DateTimeOffset.Now.UtcDateTime也可用,但不如直接UtcNow直观
自定义 payload 字段时,哪些 key 会被框架自动识别
JwtSecurityTokenHandler 会把几个标准 key 自动映射为 token 元数据:比如 exp 对应 Expires 属性,iat 对应 IssuedAt。但如果你在 ClaimsIdentity 里加了 new Claim("exp", "1234567890"),它不会覆盖 Expires,反而造成两个 exp 字段,校验时行为未定义。
- 只通过
SecurityTokenDescriptor.Expires控制过期,别在 Claims 里重复写exp - 自定义字段如
"role"、"tenant_id"可放心加进ClaimsIdentity,它们不会干扰生命周期逻辑 - 若需动态控制是否启用
nbf(生效时间),设NotBefore = DateTime.UtcNow.AddSeconds(10)即可,无需额外 Claim
验证 token 时,ValidateLifetime 默认为 true,但容易被忽略的点
很多同学只调 TokenValidationParameters.ValidateIssuerSigningKey = true,却没注意 ValidateLifetime 默认开启 —— 这意味着即使 token 签名正确,只要系统时间比 exp 晚,就会抛 SecurityTokenExpiredException。
测试时如果机器时间不准,或 token 过期后没刷新,就会卡在这一步。更隐蔽的是:某些反向代理或负载均衡器会修改请求头中的时间相关字段,间接影响验证结果。
- 生产环境务必确保服务器 NTP 同步,
date命令输出应与权威时间源误差 - 临时调试可设
ValidateLifetime = false,但上线前必须关掉 - 捕获
SecurityTokenExpiredException比捕获通用Exception更安全,避免掩盖其他错误
有效期不是加个字段就完事的事,它牵扯时间精度、时区一致性、验证链路完整性。哪怕密钥和算法都对,一个本地时间误用就能让整个鉴权流程静默失败。











