如何解码 JWT Token:结构与安全

CodeKit
jwt安全认证

什么是 JWT Token?

JSON Web Token(JWT,读作”jot”)是一种开放标准(RFC 7519),用于在各方之间安全地传输信息。JWT 在现代 Web 应用中被广泛用于身份认证和授权。它具有紧凑、URL 安全、自包含的特点——所有必要信息都存储在令牌本身之中。

一个 JWT 看起来像这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

注意两个点号(.)将令牌分成了三个不同的部分。

JWT 的三个组成部分

1. 头部(Header)

头部通常包含两个字段:令牌类型和签名算法。

{
  "alg": "HS256",
  "typ": "JWT"
}

这段 JSON 经过 Base64URL 编码后形成令牌的第一部分。常见算法包括:

  • HS256:使用 SHA-256 的 HMAC(对称加密)
  • RS256:使用 SHA-256 的 RSA 签名(非对称加密)
  • ES256:使用 P-256 曲线和 SHA-256 的 ECDSA

2. 载荷(Payload)

载荷包含声明(Claims)——关于实体(通常是用户)的陈述和附加数据。

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

注册声明包括:

声明名称描述
iss签发者谁签发了令牌
sub主题令牌所代表的用户
aud受众令牌的预期接收者
exp过期时间令牌何时过期
nbf生效时间令牌何时开始生效
iat签发时间令牌何时签发
jtiJWT ID令牌的唯一标识符

你可以添加任何自定义声明,但要避免在载荷中放置敏感数据——它不是加密的。

3. 签名(Signature)

签名用于验证令牌是否被篡改。对于 HS256:

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

签名确保了完整性,但不提供保密性。头部和载荷只是编码,并非加密。

解码 JWT

解码 JWT 非常简单,因为头部和载荷只是 Base64URL 编码的 JSON:

function decodeJWT(token) {
  const parts = token.split('.');
  if (parts.length !== 3) {
    throw new Error('无效的 JWT 格式');
  }

  const header = JSON.parse(atob(parts[0].replace(/-/g, '+').replace(/_/g, '/')));
  const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));

  return { header, payload };
}

// 使用示例
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
const decoded = decodeJWT(token);
console.log(decoded.header);  // { alg: "HS256", typ: "JWT" }
console.log(decoded.payload); // { sub: "1234567890", name: "John Doe", iat: 1516239022 }

更快捷的方式是使用 CodeKit 上的 JWT 解码器 工具,粘贴令牌即可在浏览器中即时解码和检查。

安全最佳实践

永远不要在载荷中存储敏感数据

由于载荷只是 Base64URL 编码,任何截获令牌的人都能读取其内容。绝不要在 JWT 声明中存储密码、身份证号或其他敏感信息。

始终验证签名

不验证签名就解码 JWT 是危险的。攻击者可以修改载荷并重新编码。始终使用相应的密钥或公钥验证签名:

// 在 Node.js 中使用 jsonwebtoken 库
const jwt = require('jsonwebtoken');
const decoded = jwt.verify(token, 'your-secret-key');
// 如果签名无效,会抛出错误

设置合理的过期时间

始终包含 exp 声明,并保持较短的过期时间。访问令牌通常应在 15–30 分钟后过期。使用有效期更长的刷新令牌来获取新的访问令牌。

使用 HTTPS

始终通过 HTTPS 传输 JWT。通过明文 HTTP 发送的令牌可能被网络上的任何人截获。

安全存储令牌

  • 浏览器:将令牌存储在内存或 httpOnly Cookie 中,不要存储在 localStorage(容易受到 XSS 攻击)
  • 移动端:使用平台的安全存储(iOS 的 Keychain,Android 的 EncryptedSharedPreferences)

选择合适的算法

  • HS256 更简单,但需要与所有方共享密钥
  • RS256 更适合分布式系统——签名密钥是私有的,验证密钥是公开的

常见陷阱

  1. “None” 算法攻击:某些 JWT 库接受 {"alg": "none"},这会绕过签名验证。务必显式验证算法。
  2. 通过 URL 泄露令牌:避免将令牌放在 URL 参数中——它们可能被代理和服务器记录。改用 Authorization 头。
  3. 无法撤销令牌:JWT 本质上是无状态的,撤销比较困难。考虑维护令牌黑名单或使用短过期时间。

总结

JWT 是现代 Web 应用中身份认证和授权的强大工具。理解其三段结构有助于调试问题并安全地实现它们。记住:解码是为了检查,验证才是为了信任。

想快速检查 JWT 令牌?试试 CodeKit 上的 JWT 解码器——粘贴令牌即可查看解码后的头部和载荷。