JWT Tokens Explained: Structure, Security & Decoding
Understand JSON Web Tokens from the inside out. Learn the header, payload, and signature structure, common claims, signing algorithms, and security best practices.
什么是 JWT?
JSON Web Token(JWT,读作 "jot")是一种紧凑、URL 安全的字符串,用于在双方之间传递声明。它是现代 Web 应用中最常见的身份认证与授权方式。
当你登录 Web 应用后,服务器返回的是令牌而不是在服务端创建会话时,这个令牌很可能就是 JWT。浏览器在之后的每次请求中都会带上它,服务器通过验证签名即可,而无需访问数据库。
JWT 用在哪些地方
- 身份认证。 用户登录后,服务器签发 JWT。客户端保存它,并在每次请求的
Authorization请求头中发送。 - 单点登录(SSO)。 JWT 让用户只需认证一次即可访问多个服务。
- API 授权。 微服务之间传递 JWT,以证明请求代表已认证用户且具备特定权限。
- 信息交换。 由于 JWT 经过签名,可以在各方之间传递可信数据,而无需额外的验证步骤。
JWT 的三个部分
每个 JWT 都由三个经 Base64URL 编码的部分组成,中间用点号分隔:
header.payload.signature
下面是一个真实的 JWT(为便于阅读已截短):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
我们逐段解码。
第一部分:头部(Header)
头部描述令牌类型与签名算法。从 Base64URL 解码后为:
{ "alg": "HS256", "typ": "JWT" }
alg 告诉验证方应使用哪种算法。typ 表明这是 JWT。
第二部分:载荷(Claims)
载荷包含声明(claims)——即令牌实际携带的数据。解码后为:
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }
声明是键值对。其中一部分是标准化的(「注册声明」),你也可以添加自定义声明。
载荷经 Base64URL 编码,并非加密。任何拿到令牌的人都能解码并读取载荷。切勿在 JWT 载荷中存放密钥、密码或敏感个人信息。
第三部分:签名(Signature)
签名由编码后的头部、一个点号、编码后的载荷拼接后,再用密钥签名得到:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
签名保证令牌未被篡改。
动手试试: 将任意 JWT 粘贴到我们的 JWT 解码器,即可立即查看三个部分。
常见 JWT 声明
JWT 规范(RFC 7519)定义了以下注册声明:
| Claim | 全称 | 说明 |
|---|---|---|
sub | Subject | 令牌主体(通常为用户 ID) |
iss | Issuer | 签发方 |
aud | Audience | 预期接收方 |
exp | Expiration Time | Unix 时间戳,超过后令牌无效 |
iat | Issued At | 令牌创建时的 Unix 时间戳 |
nbf | Not Before | 在此时间戳之前令牌无效 |
jti | JWT ID | 唯一标识,用于防止令牌复用 |
自定义声明即你添加的其他字段。自定义声明应尽量精简。
签名算法对比
| 算法 | 类型 | 密钥 | 适用场景 |
|---|---|---|---|
HS256 | 对称(HMAC) | 单一共享密钥 | 签发方与验证方为同一服务的简单场景 |
RS256 | 非对称(RSA) | 私钥签名,公钥验证 | 分布式系统、SSO |
ES256 | 非对称(ECDSA) | 私钥签名,公钥验证 | 与 RS256 类似,但密钥更短、速度更快 |
**对称(HS256)**更简单:双方共享同一密钥。**非对称(RS256、ES256)**更适合分布式架构。
安全最佳实践
- 切勿在载荷中存放密钥。 若需要加密令牌,请使用 JWE。
- 使用较短的有效期。 访问令牌建议 5–15 分钟。
- 始终使用 HTTPS。
- 校验所有声明。 务必检查
exp、iss和aud。 - 不要接受
alg: none。 - 对算法使用白名单。
- 安全存储令牌。
HttpOnlyCookie 比localStorage更安全。
常见 JWT 误区
- 把 JWT 放在 URL 里。
- 完全不验证签名。
- 为 HS256 使用弱密钥。
- 令牌永不过期。
- 在令牌里塞入过多数据。
令牌刷新流程
- 用户登录。服务器返回访问令牌(短效)和刷新令牌(长效)。
- 客户端在每次 API 请求中携带访问令牌。
- 访问令牌过期后,API 返回 401。
- 客户端将刷新令牌发送到
/refresh端点。 - 服务器校验后签发新的访问令牌(并可选择签发新的刷新令牌——刷新令牌轮换)。
- 客户端用新的访问令牌重试请求。
// Simplified refresh flow (client-side)
const handleApiRequest = async (url, options) => {
let response = await fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${getAccessToken()}`,
},
});
if (response.status === 401) {
const refreshResponse = await fetch("/api/refresh", {
method: "POST",
body: JSON.stringify({ refresh_token: getRefreshToken() }),
});
if (refreshResponse.ok) {
const { access_token, refresh_token } = await refreshResponse.json();
saveTokens(access_token, refresh_token);
response = await fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${access_token}`,
},
});
} else {
redirectToLogin();
}
}
return response;
};
JWT 与会话:如何选择
| 方面 | JWT | 服务端会话 |
|---|---|---|
| 状态存储 | 客户端(服务端无状态) | 服务端(会话存储 / 数据库) |
| 扩展性 | 较易——无共享状态 | 需要共享会话存储 |
| 吊销 | 较难——需黑名单或短过期 | 较易——删除会话即可 |
| 载荷体积 | 较大 | 较小(通常仅会话 ID Cookie) |
| 跨域 | 较友好(Bearer 令牌) | 需配置 CORS 与 Cookie |
| 微服务 | 较理想——各服务独立验证 | 各服务需查询会话存储 |
| 实现复杂度 | 环节更多 | 正确实现相对更简单 |
适合用 JWT: 跨分布式服务的无状态认证、移动应用或第三方 API 消费者。 适合用会话: 需要便捷吊销、单体应用,或追求最简单的安全方案。
动手试试: 使用我们的 JWT 解码器 即时解码并检查任意 JWT——数据不会离开你的浏览器。