乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • JWT 基础

    • JWT 概述
    • JWT 结构详解
  • 签名与加密

    • JWS 签名基础
    • RS256与ES256签名
    • JWE 加密
  • 密钥管理

    • JWK 与密钥管理
  • 算法原理

    • 核心算法详解
  • 实战与安全

    • JWT 实际应用
    • JWT 安全攻防

JWT 结构详解

本教程结合 广州飞翔科技 (learnto.cn)的实际场景进行讲解。


JWT 的三部分结构

所有 JWT 都由三个不同的元素构成:

  • 头部(Header) :包含关于 JWT 自身的元数据,如算法、类型等。
  • 载荷(Payload) :包含所有用户数据和标准声明。
  • 签名/加密数据(Signature / Encryption Data) :用于验证真实性或保护数据。对于未加密的 JWT,此部分被省略。

这三个元素经过 Base64URL 编码后,由点号(.)连接,形成紧凑序列化(Compact Serialization)表示:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

Base64URL 编码

JWT 使用一种对 URL 安全的 Base64 编码变体,称为 Base64URL 。

与普通 Base64 相比,它做了两处关键调整:

调整项普通 Base64Base64URL
第 62 个字符+-
第 63 个字符/_
填充字符=移除

注:JWT 规范 不要求 在编码之前将 JSON 压缩或去除无意义的空白字符。


Header(头部)详解

每个 JWT 都携带一个头部(也称为 JOSE 头部 ),其中包含关于自身的声明。未加密 JWT 头部唯一必填的声明是 alg:

  • alg :用于签名和/或解密此 JWT 的主要算法。对于未加密的 JWT,必须设置为 none。

可选声明包括:

  • typ :媒体类型(media type)。当存在时,应设置为 JWT。仅在 JWT 可能与其他 JOSE 对象混合使用时才有必要。
  • cty :内容类型(content type)。对于载荷本身是 JWT 的情况(嵌套 JWT), 必须 设置为 JWT。普通场景下 不得 设置。

例如,一个未受保护 JWT 的头部:

{
    "alg": "none"
}

编码后为:

eyJhbGciOiJub25lIn0

Payload(载荷)详解

载荷是添加所有用户数据的地方。规范定义了具有特定含义的 注册声明(registered claims) ,以及用户自定义的公共/私有声明。

七个注册声明(Registered Claims)

声明全称含义
ississuer(签发者)唯一标识签发此 JWT 的方
subsubject(主题)唯一标识此 JWT 所携带信息的主体(如用户 ID)
audaudience(受众)标识此 JWT 的预期接收者,接收方必须在其中找到自己
expexpiration(过期时间)POSIX 时间戳,超过此时间 JWT 被视为无效
nbfnot before(生效时间)POSIX 时间戳,在此之前 JWT 被视为无效
iatissued at(签发时间)POSIX 时间戳,表示 JWT 的签发时刻
jtiJWT ID此 JWT 的唯一标识符,可用于防止重放攻击

所有名称都很短,这是 JWT 的设计需求之一: 使令牌尽可能紧凑 。

公共声明与私有声明

  • 私有声明(Private claims) :由 JWT 的用户(消费者和生产者)自行定义,用于特定场景。需注意防止命名冲突。
  • 公共声明(Public claims) :在 IANA JSON Web Token Claims 注册表中注册过的声明,或使用抗冲突命名空间(如带前缀)的声明。

在实践中,大多数 JWT 使用的是注册声明和私有声明。

注意:重复声明(重复的 JSON 键)通常保留最后一次出现的值。为避免兼容性问题,建议不要重复声明。


未受保护的 JWT(Unsecured JWTs)

当头部中 alg 设置为 none 时,JWT 不包含签名或加密。它仅由头部和载荷组成,紧凑表示中尾部仍保留点号:

// 头部
{ "alg": "none" }

// 载荷
{
    "sub": "user123",
    "session": "ch72gsb320000udocl363eofy",
    "name": "Pretty Name",
    "lastpage": "/views/settings"
}

编码后:

eyJhbGciOiJub25lIn0.
eyJzdWIiOiJ1c2VyMTIzIiwic2Vzc2lvbiI6ImNoNzJnc2IzMjAwMDB1ZG9jbDM2M2VvZnkiLCJuYW1lIjoiUHJldHR5IE5hbWUiLCJsYXN0cGFnZSI6Ii92aWV3cy9zZXR0aW5ncyJ9.

注意末尾的点号——签名部分为空字符串,但点号仍然保留。

未受保护的 JWT 在特定场景下可能适用(如会话 ID 难以猜测、数据仅用于客户端视图构建),但在 生产环境中极为罕见 。


编码与解码示例代码

创建未受保护的 JWT

// URL-safe variant of Base64
function b64(str) {
    return new Buffer(str).toString('base64')
        .replace(/=/g, '')
        .replace(/\+/g, '-')
        .replace(/\//g, '_');
}

function encode(h, p) {
    const headerEnc = b64(JSON.stringify(h));
    const payloadEnc = b64(JSON.stringify(p));
    return `${headerEnc}.${payloadEnc}`;
}

解析未受保护的 JWT

function decode(jwt) {
    const [headerB64, payloadB64] = jwt.split('.');
    // These supports parsing the URL safe variant of Base64 as well.
    const headerStr = new Buffer(headerB64, 'base64').toString();
    const payloadStr = new Buffer(payloadB64, 'base64').toString();
    return {
        header: JSON.parse(headerStr),
        payload: JSON.parse(payloadStr)
    };
}

飞翔科技实战场景:大翔的登录令牌结构解析

广州飞翔科技 的 CEO/CTO大翔 每天登录内部管理系统时,系统会返回一个 JWT。让我们拆解这个令牌的内部结构。

大翔的令牌载荷如下:

{
    "iss": "learnto.cn",
    "sub": "daxiang-ceo",
    "aud": "admin-system",
    "exp": 1718000000,
    "iat": 1717996400,
    "jti": "fxg-token-2024-001",
    "role": "admin",
    "dept": "技术部",
    "permissions": ["read", "write", "delete"]
}

后端开发 小崔 解释:

"iss 标明是飞翔科技签发的,sub 是大翔的用户标识,aud 限制这个令牌只能用于 admin-system。exp 和 iat 控制有效期,jti 用于防止重放。后面的 role、dept、permissions 是我们业务需要的私有声明。"

产品经理 孔蓝 追问:"如果用户把令牌复制到另一个浏览器能用吗?"小崔回答:"能,因为 JWT 本身是无状态的。所以我们把有效期设得很短,再配合刷新令牌机制。"

UI 设计师 林鸥 则负责在令牌即将过期时,用优雅的动画提示用户重新登录,保证体验流畅。


小结

  • JWT 由 Header(头部) 、 Payload(载荷) 和 Signature(签名) 三部分组成,通过点号连接。
  • 使用 Base64URL 编码:将 + 换成 -,/ 换成 _,并移除填充 =。
  • Header 必填 alg,可选 typ 和 cty。
  • Payload 包含 7 个注册声明(iss、sub、aud、exp、nbf、iat、jti)以及公共/私有声明。
  • 未受保护的 JWT(alg: none)在生产环境中极为罕见,尾部点号仍需保留。
  • 编码和解码过程简单,可用原生 JavaScript 实现。

在下一篇教程中,我们将进入 JWS(JSON Web Signature) 的世界,学习如何用签名保护 JWT 数据不被篡改。

上一页
JWT 概述