跳至正文
老丹的足迹 —— 代码写给机器,游记写给自己,感悟写给时间
老丹的足迹 老丹的足迹
老丹的足迹 老丹的足迹
  • 首页
  • 示例页面
  • 首页
  • 示例页面
老丹的足迹 老丹的足迹
老丹的足迹 老丹的足迹
  • 首页
  • 示例页面
  • 首页
  • 示例页面

JWT 完全剖析:从编码原理到生产级实战

一、JWT 不是某种特定算法,而是一种“格式规范”

首先必须理清一个根本认知:JWT 不是加密算法,也不是认证协议,它仅仅是一种字符串格式的规范(RFC 7519)。就像 JSON 是一种数据格式,XML 也是一种数据格式一样。

任何符合以下三段式结构的字符串,都可以称为 JWT:

Header.Payload.Signature

它的核心设计哲学是:把数据(Payload)和防伪签名(Signature)绑在一起,形成一个不可分割的自包含凭证。

二、逐层解剖:三段到底长什么样?(手拆一个真实 Token)

我们随便生成一个 JWT 示例:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4iLCJpYXQiOjE1MTYyMzkwMjJ9.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

第一段:Header(头部)—— 元数据声明

解码后(Base64Url 解码):

{
  "alg": "HS256",   // 签名算法:HMAC SHA-256
  "typ": "JWT"      // 令牌类型
}
  • alg 决定了第三段签名用哪种算法生成。常见的有 HS256(对称加密)、RS256(非对称加密)、ES256(椭圆曲线)。
  • typ 通常固定为 JWT,但有些场景会写成 JOSE(JSON Object Signing and Encryption)。

重要细节:如果 alg 被改成 none,某些旧版本库会跳过签名验证,这是严重的安全漏洞(CVE-2015-9235)。生产环境必须严格校验算法。

第二段:Payload(负载)—— 真正的业务数据

解码后:

{
  "sub": "1234567890",     // 标准声明:主题(通常放用户ID)
  "name": "John",          // 私有声明:业务自定义字段
  "iat": 1516239022        // 标准声明:签发时间(Issued At)
}

JWT 规范预定义了 7 个标准声明(Registered Claims),强烈建议使用:

声明全称含义
issIssuer签发者(如 "iss": "auth.myapp.com")
subSubject主题(通常是用户唯一标识)
audAudience受众(指定该 token 给谁用,防止被其他系统滥用)
expExpiration Time过期时间(Unix 时间戳,必须设置)
nbfNot Before生效时间(在此之前 token 无效)
iatIssued At签发时间
jtiJWT ID唯一标识(可用于防止重放攻击)

生产铁律:exp 是强制要求,未设置过期时间的 JWT 等于在系统里开了永久后门。

⚠️ 极度重要:Payload 仅仅做了 Base64Url 编码,不是加密!任何人都可以粘贴到 jwt.io 上直接看到明文。所以:

  • ✅ 可以放:user_id、role、nickname(非敏感展示信息)
  • ❌ 绝对禁止:password、credit_card、phone、email(若 email 也视为敏感)

第三段:Signature(签名)—— 安全性的命脉

签名的生成公式(以 HS256 为例):

signature = HMAC-SHA256(
    base64UrlEncode(header) + "." + base64UrlEncode(payload),
    secret_key
)

这段签名的核心价值:

  1. 防篡改:只要 Payload 或 Header 被改动任何一个字符,重新计算的签名与携带的签名就会不一致,服务端直接拒绝。
  2. 防伪造:只有持有正确 secret_key 的服务端才能生成合法签名,攻击者无法凭空造出一个有效 JWT。

对称 vs 非对称签名的选择(重要架构决策):

算法类型代表特点适用场景
对称(HS256)同一个密钥既签又验速度快,但密钥不能泄露,且难以在多个微服务间安全共享单体应用、内部网关
非对称(RS256/ES256)私钥签名,公钥验签私钥只保存在认证中心(Auth Server),各业务服务只需持有公钥即可验签,无需共享私钥微服务架构、第三方开放平台

生产建议:如果服务拆分超过 3 个,强烈建议用 RS256。这样授权中心用私钥签发,所有下游服务用公钥本地验签,完全无需网络调用,性能极高。

三、深度原理:服务端到底是怎么“验”JWT 的?

很多人以为验证 JWT 是去数据库查,其实完全不是。验证过程是纯数学计算,不涉及任何 I/O:

  1. 服务端从请求头取出 JWT,按 . 分割成三段。
  2. 取前两段(Header + Payload),用本地存储的密钥(或公钥)按照 Header 里声明的 alg 算法,重新计算一遍签名。
  3. 对比新算出的签名与第三段携带的签名是否完全一致。
  4. 如果一致,再校验 exp、nbf、aud 等时间与受众声明是否有效。
  5. 全部通过,则直接信任 Payload 里的数据,整个验证过程结束。

核心优势:这个过程就是几行 CPU 指令,耗时毫秒级,且完全不依赖数据库或 Redis,这是 JWT 让系统获得极致弹性的根本原因。

四、生产环境必须面对的三个“灵魂拷问”

1. 用户修改密码后,旧的 JWT 为什么还能用?怎么解决?

这是 JWT 最被诟病的问题:无法主动失效。因为服务端根本不存 token 状态,它无法知道这个 token 是否已被“吊销”。

业界通用解决方案:

  • 方案 A(推荐):引入 Refresh Token(刷新令牌) 机制。Access Token 有效期极短(如 15 分钟),Refresh Token 存数据库/Redis 并可随时撤销。用户改密码时,删除该用户的 Refresh Token,旧的 Access Token 虽仍有效,但 15 分钟后自动失效且无法刷新。
  • 方案 B(暴力但有效):维护一个 Token 黑名单(Redis Set)。每次请求先验签,再查黑名单是否存在该 JWT 的 jti(唯一ID)。这打破了无状态,但在高安全场景(如金融)必须这么做。
  • 方案 C(设计妥协):将用户密码版本号(password_version)编码进 JWT 的 Payload。每次校验时对比数据库中的版本号,不一致则拒绝。

2. JWT 存储在客户端的哪里最安全?

存储位置安全性是否自动携带适用场景
localStorage❌ 极易被 XSS 攻击窃取否(需手动塞 Header)不推荐存储敏感 token
sessionStorage❌ 同样受 XSS 威胁,且关闭标签页即失效否临时性、低敏感应用
Cookie(HttpOnly + Secure + SameSite=Strict)✅ 无法被 JS 读取,免疫 XSS是(浏览器自动带)最安全的前端存储方式

生产最佳实践:

  • JWT 放在 HttpOnly Cookie 中,前端完全无法读取,XSS 攻击束手无策。
  • 但要防范 CSRF,必须设置 SameSite=Strict 或配合 CSRF Token。

3. JWT 大小膨胀问题(Token 里塞太多权限)

若你在 Payload 里放了一个包含 50 个菜单项的权限数组,JWT 可能轻松突破 2KB。而每个请求的 HTTP Header 容量有限(Nginx 默认 8KB),且流量费用、网络延迟都会增加。

优化策略:

  • 只放角色 ID(如 role: "admin"),细粒度权限在服务端内存缓存中映射,不由 JWT 携带。
  • 若必须携带权限列表,使用 压缩算法(如 JWT 支持 zip 声明)或改用 引用式 Token(即存一个 Session ID 在 JWT 里,实际权限仍查缓存)。

五、JWT 与 OAuth 2.0 / OpenID Connect 的关系(很多人混淆)

  • OAuth 2.0 是一个授权框架,定义的是“如何获取访问令牌”的流程(如授权码模式、密码模式),它不规定令牌的格式。
  • JWT 是一种令牌格式。
  • OpenID Connect(OIDC) 是在 OAuth 2.0 之上构建的身份认证层,它强制规定 ID Token 必须使用 JWT 格式。

关系比喻:OAuth 2.0 是“快递取件流程”,JWT 是“快递盒子的标准规格”,OIDC 是“必须在盒子里放身份证复印件”。

在实际工作中,你用 OAuth 2.0 协议去获取一个 JWT 格式的 Access Token,这是最主流的企业级实现方式。

六、一张图总结 JWT 的完整生命周期

[用户登录] 
    → 服务端生成 JWT(Header + Payload + 签名)
    → 返回给客户端(存 Cookie 或 localStorage)
    → 客户端每次请求携带(Authorization Header 或 Cookie)
    → 服务端验签(纯本地计算,无 I/O)
    → 校验过期时间
    → 读取 Payload 中的 user_id 执行业务
    → 若 Access Token 过期,用 Refresh Token 换取新 JWT
    → (可选)退出登录时,将 jti 加入 Redis 黑名单

七、最后的深度思考:JWT 真的适合所有场景吗?

适用场景(推荐使用):

  • 前后端完全分离的 SPA(单页应用)或移动 APP
  • 微服务架构中的服务间内部认证
  • 第三方 API 访问令牌(如 GitHub、Google 登录)
  • 短时效的一次性操作链接(如邮件中的“点击重置密码”)

不适用场景(谨慎使用):

  • 需要主动踢人下线的后台管理系统(如管理后台)
  • 对安全性要求极高的金融交易(推荐结合 Session + 黑名单)
  • Payload 数据量巨大(大于 4KB)的业务

JWT 从来不是银弹,它是在分布式、可扩展、低延迟三者之间做出的优雅权衡。理解它的每一处设计取舍,你才能真正驾驭它,而不是被它的“坑”所困扰。

作者

老丹

关注我
其他文章
上一个

C++ 智能指针完全指南:从入门到工程实战

下一个

MP4文件格式完全技术手册:从入门到精通

关于博主

    老丹是一名C/C++后台开发工程师,信奉“无抽象不设计,无性能不生产”。

  • 技术栈:Modern C++、Linux环境编程、多线程/并发、网络编程等。
  • 信条:能用constexpr解决的问题绝不拖到运行时,能靠RAII避免的泄漏绝不写析构。
  • 正在填坑:从解封装到渲染的C++全链路实现,正在驯服FFmpeg与H.264/H.265。
  • 输出原则:这里的每一段代码都经过-Wall -Wextra -Werror -O2的洗礼。

近期文章

  • Ubuntu 防火墙迁移指南:从 UFW 到 firewalld 的完整实践 2026年9月12日
  • Nano 编辑器完全操作指南:从入门到熟练 2026年9月12日
  • SSCG:让自签名证书不再“危险”的生成工具 2026年9月12日
  • Ubuntu Samba 服务安装与配置完全指南 2026年9月12日
  • 从零开始:用 Docker 部署 Jellyfin 并启用英特尔核显硬件加速 2026年9月11日

文章分类

  • C/C++开发 (22)
  • Docker容器 (5)
  • Linux工具包 (17)
  • Linux服务配置 (50)
  • Linux系统 (16)
  • OpenWrt路由 (3)
  • Shell脚本 (3)
  • 代码管理 (1)
  • 安防技术 (4)
  • 数据安全 (36)
  • 未分类 (1)
  • 网络协议 (25)
  • 计算机理论 (23)
  • 音视频技术 (5)
联系我们:📍 地址:中国·广东省深圳市   |   ✉️ 邮箱:support@tanglinux.com   |   💬 QQ:870866607
版权所有:老丹的足迹粤ICP备2026061170号-1       公安备案图标 粤公网安备44030002013274号