如何解码 JWT Token:结构与安全
什么是 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 | 签发时间 | 令牌何时签发 |
jti | JWT 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 更适合分布式系统——签名密钥是私有的,验证密钥是公开的
常见陷阱
- “None” 算法攻击:某些 JWT 库接受
{"alg": "none"},这会绕过签名验证。务必显式验证算法。 - 通过 URL 泄露令牌:避免将令牌放在 URL 参数中——它们可能被代理和服务器记录。改用 Authorization 头。
- 无法撤销令牌:JWT 本质上是无状态的,撤销比较困难。考虑维护令牌黑名单或使用短过期时间。
总结
JWT 是现代 Web 应用中身份认证和授权的强大工具。理解其三段结构有助于调试问题并安全地实现它们。记住:解码是为了检查,验证才是为了信任。
想快速检查 JWT 令牌?试试 CodeKit 上的 JWT 解码器——粘贴令牌即可查看解码后的头部和载荷。