一、JWT 是什么
JWT(JSON Web Token)是一种用 JSON 格式传递认证信息的开放标准(RFC 7519)。简单说,它就是一段带签名的字符串,里面装着"你是谁、你能做什么、什么时候过期"这些信息。
一个典型的 JWT 长这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
看起来像三段乱码用 . 连起来——没错,JWT 就是由三部分组成的:
Header.Payload.Signature
JWT 最常见的使用场景是用户登录后的身份认证:用户登录成功后,服务器返回一个 JWT,客户端把它存起来,之后每次请求都带上,服务器通过验证 JWT 知道你是谁。
二、JWT 的三段结构
第一段:Header(头部)
Header 是一个 JSON 对象,描述 token 的类型和签名算法:
{
"alg": "HS256",
"typ": "JWT"
}
alg:签名算法,常见有HS256、RS256、ES256typ:固定为JWT
这个 JSON 会被 Base64URL 编码,变成 JWT 的第一段:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
第二段:Payload(载荷)
Payload 也是 JSON,装着实际要传递的数据,这些数据叫"声明"(Claims)。声明分三类:
1. 标准声明(RFC 7519 规定的)
| 字段 | 含义 |
| iss | 签发人(issuer) |
| sub | 主题(subject,通常是用户 ID) |
| aud | 接收方(audience) |
| exp | 过期时间(expiration) |
| nbf | 生效时间(not before) |
| iat | 签发时间(issued at) |
| jti | JWT 唯一 ID |
2. 私有声明(业务自定义的)
{
"sub": "1234567890",
"name": "John Doe",
"role": "admin",
"iat": 1516239022,
"exp": 1516242622
}
这个 JSON 也会被 Base64URL 编码,变成第二段:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
注意:Payload 只是 Base64 编码,不是加密。任何人都能解码看到内容,所以千万别在里面放密码、敏感信息。
第三段:Signature(签名)
签名是用来防止篡改的。算法是:
HMACSHA256(
base64url(header) + "." + base64url(payload),
secret
)
也就是把前两段用 . 拼起来,用密钥做一次 HMAC-SHA256,得到签名:
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
把三段用 . 连起来,就是完整的 JWT。
三、Base64URL 编码
JWT 用的是 Base64URL,和普通 Base64 有两点区别:
| 普通Base64 | Base64URL |
+ | - |
/ | _ |
末尾有 = | 末尾无 = |
原因是 JWT 经常放在 URL、HTTP 头里传输,+、/、= 这些字符会有歧义。
四、签名验证机制
JWT 最核心的安全机制就是签名。验证流程是:
- 服务器收到 JWT,按
.拆成三段 - 取前两段(header 和 payload),用同样的算法和密钥重新算一次签名
- 把算出来的签名和第三段对比
- 如果一致,说明内容没被篡改;不一致则拒绝
关键点:签名用的密钥只有服务器知道。攻击者即使能解出 Payload 里的内容,也修改不了——因为改了之后签名对不上,服务器会拒绝。
举个例子:
- 服务器签发:
{ "role": "user" }→ 签名abc123 - 攻击者改成:
{ "role": "admin" }→ 但他没有密钥,算不出新的正确签名 - 服务器验证时:用密钥算
{ "role": "admin" }的签名,得到xyz789,和原签名abc123不一致,拒绝
五、JWT vs Session
这是登录认证的两大主流方案,对比如下:
| 维度 | Session | JWT |
| 存储位置 | 服务器内存/Redis | 客户端 |
| 状态 | 有状态 | 无状态 |
| 扩展性 | 需要共享 session 存储 | 天然支持分布式 |
| 撤销 | 容易(删 Redis 即可) | 困劲(需黑名单) |
| 安全性 | 较高(数据在服务端) | 取决于实现 |
| 跨域 | 复杂 | 简单 |
| 体积 | 小(cookie 里只存 sessionId) | 较大(整个 token 都在请求里) |
Session 的工作方式
- 用户登录,服务器创建一个 session,存到内存或 Redis
- 返回一个 sessionId(通过 cookie)
- 下次请求带着 cookie,服务器用 sessionId 查到 session 数据
- 服务器知道用户是谁
JWT 的工作方式
- 用户登录,服务器生成一个 JWT 返回
- 客户端把 JWT 存起来(localStorage 或 cookie)
- 下次请求把 JWT 放在
Authorization: Bearer xxx头里 - 服务器验证 JWT 签名,直接从 Payload 里读用户信息
六、JWT 的优势
1. 无状态
服务器不需要存 session,每个请求自带身份信息。这意味着:
- 服务器重启不影响已登录用户
- 多台服务器之间不需要共享 session 存储
- 水平扩展非常容易
2. 跨域友好
Cookie 在跨域时会有各种限制(SameSite、CORS),而 JWT 放在 Authorization 头里,不受这些限制影响。前后端分离、移动端、第三方调用都能轻松搞定。
3. 解耦
签发 JWT 的服务和验证 JWT 的服务可以是不同的,只要共享同一个密钥(或用非对称算法的公钥)。微服务架构里特别有用。
七、JWT 的安全风险
JWT 不是银弹,用得不好会有严重的安全问题。
1. XSS 攻击
如果 JWT 存在 localStorage,攻击者通过 XSS 注入脚本就能偷走它。
应对:
- 严格做 XSS 防护(转义输出、CSP)
- 或者把 JWT 存在 HttpOnly Cookie 里,但这样又会有 CSRF 风险
2. CSRF 攻击
如果 JWT 存在 Cookie 里,攻击者可以构造一个表单,让用户在已登录状态下发起请求。
应对:
- JWT 放 Authorization 头(不用 Cookie)
- 或加 CSRF Token
3. 撤销困难
JWT 一旦签发,在过期前一直有效。如果用户改密码、退出登录、账号被封,旧的 JWT 依然能用。
应对:
- 服务端维护一个黑名单(违背了无状态的初衷)
- 缩短过期时间,配合 refresh token
- 把 token 版本存数据库,验证时检查
4. 密钥泄露
签名密钥一旦泄露,攻击者可以伪造任意用户的 JWT。
应对:
- 密钥严格保管,不进代码仓库
- 定期轮换密钥
- 用非对称算法(RS256),签发用私钥,验证用公钥
八、JWT 的实际使用示例
签发 JWT(Node.js)
const jwt = require("jsonwebtoken");
const token = jwt.sign(
{ userId: 123, role: "admin" },
"my-secret-key",
{ expiresIn: "2h" }
);
验证 JWT
try {
const payload = jwt.verify(token, "my-secret-key");
console.log(payload.userId); // 123
} catch (e) {
console.log("token 无效或已过期");
}
前端发送 JWT
fetch("/api/profile", {
headers: {
"Authorization": "Bearer " + token
}
});
九、实践推荐
调试 API 时经常需要看 JWT 里到底装了什么——尤其是接 OAuth、SSO 这种第三方登录的场景,token 又长又看不懂。在线解码工具是最快的:
- JWT 在线解码工具:https://52tool.net/tools/code/jwtDecode
粘贴 JWT 后自动解码 Header 和 Payload,高亮显示关键字段(exp、iat、sub),还能检查签名算法和过期时间,是调试鉴权问题时最常用的工具之一。