
什么是 JWT?
JSON Web Token(JWT) 是一种紧凑、URL 安全的字符串,用于在双方之间传递经过验证的声明。可以把它想象成一张签名的数字票据:服务器签发它,客户端存储它,每次后续请求都将其作为身份证明提交。
JWT 是自包含的——令牌本身携带所需数据,因此服务器无需在每次请求时都查询数据库。

JWT 的三部分
一个 JWT 看起来像这样:
xxxxx.yyyyy.zzzzz
用点号分隔后,得到三个 Base64URL 编码的部分:
1. 头部(Header)
{
"alg": "HS256",
"typ": "JWT"
}
声明令牌类型和签名算法。常见算法:HS256(HMAC-SHA256,对称)、RS256(RSA,非对称)、ES256(ECDSA)。
2. 载荷(Payload / Claims)
{
"sub": "user_123",
"name": "Alice",
"role": "admin",
"iat": 1716825600,
"exp": 1716829200
}
包含实际数据(“声明”)。标准注册声明:
| 声明 | 含义 |
|---|---|
sub |
主题(用户 ID) |
iss |
签发者 |
aud |
受众 |
exp |
过期时间(Unix 时间戳) |
iat |
签发时间 |
nbf |
生效时间 |
重要:载荷仅经过 Base64URL 编码,并未加密。任何人都可以解码。切勿在载荷中放置密码或敏感的个人身份信息。
3. 签名(Signature)
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
签名用于验证令牌未被篡改。如果头部或载荷的任何一位发生变化,签名将失效。

JWT 认证工作原理(分步说明)
1. 用户发送凭据(用户名 + 密码)到 POST /login
2. 服务器验证凭据
3. 服务器创建包含用户声明的 JWT,并使用密钥签名
4. 服务器将 JWT 返回给客户端
5. 客户端存储 JWT(内存、localStorage、httpOnly cookie)
6. 客户端在每次请求中发送 JWT:Authorization: Bearer <token>
7. 服务器在每次请求时验证签名——无需数据库查询
8. 服务器直接从载荷中读取声明
JWT 与 Session 令牌对比
| 因素 | JWT | Session |
|---|---|---|
| 存储位置 | 客户端 | 服务器端(数据库/Redis) |
| 可扩展性 | 无状态,易于扩展 | 需要粘性会话或共享存储 |
| 撤销 | 困难(必须等待过期) | 容易(从存储中删除) |
| 载荷 | 携带数据 | 仅一个不透明 ID |
| 大小 | 较大(约 500 字节以上) | 较小(16–32 字节) |
| 最佳适用场景 | API、微服务 | 单体 Web 应用 |
经验法则:对于需要即时注销的传统 Web 应用,使用 Session;对于 API 和分布式系统,使用 JWT。
常见 JWT 安全错误
1. 使用 "alg: none"
一些早期库在 alg 设置为 "none" 时会接受未签名的令牌。始终显式验证算法:
jwt.verify(token, secret, { algorithms: ['HS256'] });

2. 将 JWT 存储在 localStorage 中
localStorage 可通过 JavaScript 访问,容易受到 XSS 攻击。对于基于浏览器的应用,优先使用 httpOnly cookie。
3. 服务之间共享对称密钥
使用 HS256 时,每个服务都需要相同的密钥来验证令牌——这意味着任何服务也可以签发令牌。使用 RS256/ES256,这样只有认证服务拥有私钥;其他服务只需要公钥。
4. 过期时间过长且无刷新机制
一个 30 天有效且无法撤销的 JWT 是安全负担。推荐模式:
- 访问令牌:短生命周期(15 分钟)
- 刷新令牌:长生命周期(7–30 天),存储在 httpOnly cookie 中,每次使用后轮换
5. 未验证 exp
始终验证过期时间。大多数库会自动执行此操作,但请确认它未被禁用。
无需库解码 JWT
function decodeJwt(token) {
const [headerB64, payloadB64] = token.split('.');
const decode = (str) => JSON.parse(atob(str.replace(/-/g, '+').replace(/_/g, '/')));
return {
header: decode(headerB64),
payload: decode(payloadB64),
};
}
const { payload } = decodeJwt(myToken);
console.log(payload.sub); // user_123
常见问题
问:可以在没有 HTTPS 的情况下使用 JWT 吗? 不可以。始终使用 HTTPS。没有 TLS,令牌可能被拦截并重用(重放攻击)。
问:如何在 JWT 过期前使其失效? 选项:维护服务器端黑名单/拒绝列表、使用短过期时间 + 刷新令牌、或更改签名密钥(会使所有令牌失效)。
问:JWT 载荷的最大大小是多少? 没有硬性限制,但请保持较小(1 KB 以下)。较大的令牌会增加请求开销。避免存储每次请求都需要从数据库查询的内容。
问:JWT 和 OAuth 是一样的吗? 不是。JWT 是一种令牌格式。OAuth 2.0 是一种授权协议,可以使用 JWT 作为其令牌格式。
→ 使用 JWT 解析器 检查并解码任何 JWT 令牌。