升级一个 Rust JWT 库能有多难?

升级一个 Rust JWT 库能有多难?

💡 原文英文,约2400词,阅读约需9分钟。
📝

内容提要

作者在维护客户Rust项目时发现jwt库落后12个小版本,升级后新旧版本生成的JWT令牌不兼容,将导致约10万匿名用户令牌失效和数据丢失。作者通过修改库源码解决:显式设置Header的typ字段、移除claims中跳过空字段的serde属性、调整字段顺序、修正签名base64url编码,最终新旧令牌一致,修改版已用于生产。

🔎

延伸解读

小版本升级为何会破坏令牌兼容性

文章指出,jwt 库从 0.4.0 到 0.16.0 虽只跨小版本,但 0.6.0 已引入新 Token API,且底层加密库从 rust-crypto 迁移到 RustCrypto。这些改动改变了 Header 和 Claims 的序列化行为,导致新旧版本生成的 JWT 令牌不兼容。这提醒开发者,依赖库的小版本更新也可能包含影响数据格式的变更,不能仅凭版本号判断升级风险。

匿名用户场景放大了升级风险

该项目的 JWT 令牌存储在用户设备上用于认证,且支持“免注册使用”,匿名用户也会持有令牌。若解码逻辑不兼容,这些用户将无法重新登录,从而丢失数据。作者估算受影响用户约 10 万。这说明在涉及持久化令牌的架构中,升级认证库前必须评估令牌失效对无法重新认证用户的影响。

兼容性修复的关键点

作者通过修改库源码使新旧令牌一致:显式设置 Header 的 typ 字段、移除 Claims 中跳过空字段的 serde 属性、调整字段顺序以匹配旧版序列化顺序,并修正签名 base64url 编码(将 + 替换为 -、/ 替换为 _)。这些修改表明,JWT 兼容性不仅取决于声明内容,还依赖字段顺序、空值处理和编码细节。

调试工具本身也可能误导

作者最初用 jwt-cli 解码对比,发现新旧令牌解码结果相同,但实际令牌仍不同。原因是 jwt-cli 底层使用的 jsonwebtoken 库同样会跳过空字段,掩盖了差异。最终通过 jwt.io 对比才定位到 Header 字段顺序和签名编码问题。这提示开发者,验证令牌兼容性时应使用多种工具交叉检查,避免单一工具的输出造成误判。

❓

Q&A

为什么升级 Rust JWT 库会导致用户数据丢失?

因为新旧版本生成的 JWT 令牌不兼容,旧令牌无法被新版本验证,导致约 10 万匿名用户被登出且无法重新登录,从而丢失数据。

rust-jwt 从 0.4.0 升级到 0.16.0 后,生成的令牌有哪些主要差异?

新版本生成的令牌更短,因为默认移除了 claims 中的空字段(null),并且 Header 中缺少 typ 字段,字段顺序也不同。

如何让新版本 rust-jwt 生成的令牌包含 typ 字段?

可以显式设置 Header 的 type_ 字段为 Some(HeaderType::JsonWebToken),或者修改库的 Default 实现,使 Header::default() 包含 typ。

为什么新版本生成的 JWT claims 中缺少空字段?

因为 RegisteredClaims 结构体中的字段使用了 serde 的 skip_serializing_if = "Option::is_none" 属性,导致 None 值被跳过序列化。

如何修正 rust-jwt 签名部分的 base64url 编码问题?

在验证签名前,将签名字符串中的 '+' 替换为 '-','/' 替换为 '_',以符合 base64url 编码规范。

作者最终如何解决令牌不兼容问题?

作者修改了 rust-jwt 库的源码:显式设置 Header 的 typ 字段、移除 claims 中跳过空字段的 serde 属性、调整字段顺序、修正签名 base64url 编码,使新旧令牌一致,并将修改版用于生产。

🏷️

标签

➡️

继续阅读