JWT从入门到精通
JWT的概念对于我来说是来的比较晚的了,但并不妨碍它是一个优秀的跨域认证方案。
一、什么是token
JWT(JSON Web Token)是一种用于身份验证和信息安全传输的开放标准(RFC 7519)。它以 JSON 格式存储信息,并使用数字签名确保安全性,常用于前后端分离认证和无状态身份验证。

二、JWT的特点
- 无状态 & 轻量化(Stateless & Lightweight)
- JWT 本身包含了用户信息,不依赖服务器存储 Session,适用于分布式系统。
- 服务器只需验证 JWT 的签名,不需要维护会话数据,减少服务器压力。
- 安全性(Security)
- 签名机制:JWT 使用 HMAC(HS256)或 RSA(RS256)进行签名,防止数据篡改。
- 支持加密:虽然 JWT 默认不加密,但可以用 JWE(JSON Web Encryption)加密 Payload,保护敏感信息。
- 跨平台 & 兼容性(Cross-platform & Compatibility)
- JWT 采用 JSON 格式,前端(JavaScript)、后端(Node.js、Python、Java 等)、移动端(iOS、Android)都支持。
- 适用于 Web、APP、小程序、微服务等各种场景。
- 自包含(Self-contained)
- JWT 里面包含了用户的身份信息(如
userId、role、exp等),服务器可以直接解析使用,无需数据库查询。
- JWT 里面包含了用户的身份信息(如
- 易于传输(Compact & URL-safe)
- JWT 是 Base64 编码的字符串,体积小,适合通过 HTTP 头部、URL、Cookie 进行传输。
- 示例:
jseyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 .eyJ1c2VySWQiOjEyMywicm9sZSI6ImFkbWluIiwiZXhwIjoxNzAwMDAwMDB9 .KkN3H8J1JZ5LnpI6LbXG - KI9mJHbRkoRCQ; - 支持 Token 过期控制(Expiration)
- 可以通过
exp(过期时间)和iat(签发时间)控制 JWT 有效期,防止 Token 长期有效带来的安全隐患。 - 示例:
json{ "userId": 123, "role": "admin", "exp": 1700000000 } - 可以通过
- 可扩展(Extendable)
- JWT 除了基本的用户身份信息,还可以自定义 Payload,存储额外的数据,如
permissions(权限)、iat(签发时间)等。”
- JWT 除了基本的用户身份信息,还可以自定义 Payload,存储额外的数据,如
三、JWT的数据结构
JWT(JSON Web Token)由 三个部分 组成:
- Header(头部)
- Payload(载荷)
- Signature(签名)
它们之间用 .(点号) 连接,例如:xxxxx.yyyyy.zzzzz

1. Header(头部)
头部通常包含两个部分:
- alg(算法):用于指定签名的加密算法(如 HS256、RS256)。
- typ(类型):标明 Token 类型,通常是
"JWT"。
示例(JSON 格式):
{ "alg": "HS256", "typ": "JWT" }然后进行 Base64 编码,形成 JWT 的第一部分。
2. Payload(载荷)
Payload 主要包含 声明(Claims),用于存储用户信息和其他自定义数据。 常见的 Claims:
- 标准声明(Registered Claims):
iss(Issuer):签发者sub(Subject):主题(通常是用户 ID)aud(Audience):接收者exp(Expiration):过期时间(时间戳,Unix 时间)iat(Issued At):签发时间nbf(Not Before):生效时间
- 自定义声明(Custom Claims): 你可以添加任何自定义字段,比如
userId、role、permissions等。
示例(JSON 格式):
json
{ "sub": "1234567890", "name": "John Doe", "role": "admin", "exp": 1710000000 }然后进行 Base64 编码,形成 JWT 的第二部分。
3. Signature(签名)
签名用于确保 JWT 数据完整性,防止被篡改。
签名的计算方式如下:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret);- 前两部分(Header + Payload) 进行 Base64 编码
- 用密钥(Secret)进行加密,生成签名 如果 JWT 被篡改,签名就会不匹配,服务器会拒绝请求。 示例(伪代码):
HMACSHA256(
`eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZSI6ImFkbWluIiwiZXhwIjoxNzEwMDAwMDAwfQ
`,
"your-secret-key",
);完整 JWT 示例
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZSI6ImFkbWluIiwiZXhwIjoxNzEwMDAwMDAwfQ
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c;其中:
- 第一部分(Header):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 - 第二部分(Payload):
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZSI6ImFkbWluIiwiZXhwIjoxNzEwMDAwMDAwfQ - 第三部分(Signature):
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c”
四、JWT的基本用法
1. 生成 JWT
const jwt = require("jsonwebtoken");
const payload = { userId: 123, role: "admin" };
const secretKey = "your-secret-key";
const token = jwt.sign(payload, secretKey, { expiresIn: "1h" });
console.log("JWT:", token);2. 验证 JWT
try {
const decoded = jwt.verify(token, secretKey);
console.log("Decoded:", decoded);
} catch (err) {
console.log("Invalid token");
}五、JWT存在的问题
1. 无法主动撤销(Token 失效控制问题)
问题:JWT 生成后,如果没有内置失效机制(比如短生命周期),它会一直有效,直到过期。
传统 Token 可以在服务器端存储,并在用户登出或 Token 失效时删除,但 JWT 本身是自包含的,服务器不会存储它,所以无法手动撤销。
解决方案:
- 缩短 JWT 过期时间(Exp),比如 10~30 分钟,然后使用 Refresh Token 机制 让用户续签。
- 引入黑名单机制,在 Redis 或数据库中存储已失效的 JWT,验证时检查是否在黑名单中。
- 引入黑名单机制,代码如下(推荐)
jsconst redisClient = require("ioredis")(); // 用户登出时,把 Token 存入 Redis 黑名单 app.post("/logout", async (req, res) => { const token = req.headers.authorization?.split(" ")[1]; if (!token) return res.status(400).json({ message: "未提供 Token" }); try { const decoded = jwt.verify(token, "SECRET_KEY"); // 把 token 存入黑名单,设置过期时间为 token 的 exp 时间 await redisClient.set( `blacklist:${token}`, "invalid", "EX", decoded.exp - Math.floor(Date.now() / 1000), ); res.json({ message: "Token 已失效" }); } catch (err) { res.status(401).json({ message: "无效的 Token" }); } }); // 服务器验证 Token 时,先检查是否在黑名单 app.get("/protected", async (req, res) => { const token = req.headers.authorization?.split(" ")[1]; if (!token) return res.status(401).json({ message: "未提供 Token" }); const isBlacklisted = await redisClient.get(`blacklist:${token}`); if (isBlacklisted) return res.status(401).json({ message: "Token 已失效" }); try { const decoded = jwt.verify(token, "SECRET_KEY"); res.json({ message: "访问成功", user: decoded }); } catch (err) { res.status(401).json({ message: "无效的 Token" }); } });- **换秘钥让所有token失效(强烈不推荐)**所有用户都会重新登录,体验不好,不推荐
jsconst SECRET_KEY = "new-secret-key";- Token 版本号机制(适用于修改权限)
js// 用户登录时,生成一个版本号 const SECRET_KEY = "your-secret-key"; // 生成 Token(带 tokenVersion) app.post("/login", async (req, res) => { const userId = "123"; const tokenVersion = 1; // 从数据库获取 const token = jwt.sign({ userId, tokenVersion }, SECRET_KEY, { expiresIn: "1h", }); res.json({ token }); }); // 保护 API(检查 tokenVersion) app.get("/protected", async (req, res) => { const token = req.headers.authorization?.split(" ")[1]; if (!token) return res.status(401).json({ message: "未提供 Token" }); try { const decoded = jwt.verify(token, SECRET_KEY); // 获取数据库中的 tokenVersion(假设从数据库查到 version = 2) const storedVersion = 2; if (decoded.tokenVersion !== storedVersion) { return res.status(401).json({ message: "Token 已失效" }); } res.json({ message: "访问成功", user: decoded }); } catch (err) { res.status(401).json({ message: "无效的 Token" }); } });
2. JWT 过大,占用带宽
问题:
- JWT 包含 Header、Payload 和 Signature,通常比传统 Token 更大,尤其当 Payload 里存了很多用户信息时。
- JWT 可能会被存入 Cookie,每次请求都会携带,影响网络性能。
- 若使用 HTTP 头(Authorization: Bearer xxx),请求头也会变大。 解决方案:
- 只在 JWT 内存储 必要信息(比如
userId),不要存太多数据。 - 使用压缩(比如 Brotli)减少网络传输数据量。
- 结合 Session Token,使用更小的 Token 存 JWT,减少带宽占用。
3. 不能修改已签发的 JWT
问题:JWT 一旦生成,其内容(Payload)就不能修改,否则签名会失效。
- 如果用户的权限变更(比如被禁用、角色修改),已签发的 JWT 仍然有效,用户可能仍然能访问某些资源。
- 传统 Token 方案可以在数据库里更新用户权限,而 JWT 必须等它过期或者主动撤销。
解决方案:
- 使用短生命周期 JWT(比如 10 分钟),结合 Refresh Token,定期刷新用户权限。
- 在服务器端维护 Token 版本号,如果用户权限变更,修改数据库中的版本号,JWT 校验时检查版本号是否匹配,不匹配就拒绝请求。
4. 安全性问题(可被篡改、泄露等)
(1) JWT 可被解码
- JWT 是 Base64 编码 的,并不是加密的。任何人都可以解码 JWT,查看 Payload 里的数据。
- 解决方案:
- 不要在 JWT 里存放敏感信息(如密码、信用卡号)。
- 使用 HTTPS,避免中间人攻击。
(2) JWT 一旦泄露,就无法回收
如果 JWT 被攻击者截获,就能伪造身份进行操作,直到 JWT 过期。
解决方案:
- 使用短生命周期的 JWT,结合 Refresh Token 限制长期有效性。
- 绑定 JWT 到 IP 或设备 ID,若请求来源 IP 变更,则拒绝请求。
- 代码如下 生成 JWT 时,将 IP 地址和设备 ID 作为 Payload 内容的一部分。
js// 首先,生成 JWT 时,可以将请求来源的 IP 地址和设备 ID 作为额外的字段加入到 Payload 中。 app.get("/protected", (req, res) => { const token = req.headers.authorization?.split(" ")[1]; if (!token) return res.status(401).json({ message: "未提供 Token" }); try { const decoded = jwt.verify(token, SECRET); // 获取请求的 IP 和设备 ID const currentIp = req.ip; const currentDeviceId = req.body.deviceId; // 或从请求头中获取设备 ID // 验证 Token 中的 IP 和设备 ID 是否与当前一致 if (decoded.ip !== currentIp || decoded.deviceId !== currentDeviceId) { return res.status(401).json({ message: "请求来源不一致,Token 失效" }); } res.json({ message: "访问成功", user: decoded }); } catch (err) { res.status(401).json({ message: "Token 无效" }); } }); // ==================分割线================= // 在后续的请求中,服务器需要验证 JWT 中存储的 IP 地址和设备 ID 是否与请求中的 IP 和设备 ID 一致。如果不一致,则拒绝请求。 // 验证 Token 并检查 IP 和设备 ID app.get("/protected", (req, res) => { const token = req.headers.authorization?.split(" ")[1]; if (!token) return res.status(401).json({ message: "未提供 Token" }); try { const decoded = jwt.verify(token, SECRET); // 获取请求的 IP 和设备 ID const currentIp = req.ip; const currentDeviceId = req.body.deviceId; // 或从请求头中获取设备 ID // 验证 Token 中的 IP 和设备 ID 是否与当前一致 if (decoded.ip !== currentIp || decoded.deviceId !== currentDeviceId) { return res.status(401).json({ message: "请求来源不一致,Token 失效" }); } res.json({ message: "访问成功", user: decoded }); } catch (err) { res.status(401).json({ message: "Token 无效" }); } });
(3) 签名算法安全性
- 如果使用 HS256(对称加密),密钥泄露后,任何人都能伪造 JWT。
- 建议使用 RS256(非对称加密),公钥验证,私钥签名,保证密钥安全性。
5. 不适用于所有认证场景
JWT 适用于 无状态认证(比如 API 认证),但在 需要频繁更新权限、可控的会话管理 中表现不佳,比如:
- 管理后台系统:管理员权限变更较频繁,JWT 不能即时更新权限,可能导致安全问题。
- 高安全性应用(如银行系统):无法主动撤销 JWT,存在安全隐患。
解决方案:
对于需要动态权限管理的系统,可以使用 Session + JWT 混合方案,比如:
- JWT 仅用于 短时间授权,用户登录后获取短期 JWT(比如 10 分钟)。
- Session 机制存储 长期会话,用于权限管理和 Token 失效控制。
- 代码如下
jsconst jwt = require("jsonwebtoken"); const redis = require("ioredis"); const redisClient = new redis(); const SECRET = "mySecretKey"; // 生成 JWT function generateToken(user) { return jwt.sign({ id: user.id, role: user.role }, SECRET, { expiresIn: "1h", }); } // API 请求时,验证 JWT & 检查黑名单 app.get("/protected", async (req, res) => { const token = req.headers.authorization?.split(" ")[1]; if (!token) return res.status(401).json({ message: "未提供 Token" }); // 检查是否在黑名单 const isBlacklisted = await redisClient.get(`blacklist:${token}`); if (isBlacklisted) return res.status(401).json({ message: "Token 失效" }); try { const decoded = jwt.verify(token, SECRET); res.json({ message: "访问成功", user: decoded }); } catch (err) { res.status(401).json({ message: "Token 无效" }); } }); // 修改权限 -> 让当前 JWT 失效 app.post("/changeRole", async (req, res) => { const token = req.headers.authorization?.split(" ")[1]; if (!token) return res.status(400).json({ message: "未提供 Token" }); // 把当前 Token 加入黑名单(存 1 小时) await redisClient.set(`blacklist:${token}`, "1", "EX", 3600); res.json({ message: "权限变更,Token 失效,请重新登录" }); });
6. JWT 需要额外的存储管理(Refresh Token)
问题:
- JWT 本身是无状态的,但如果使用 Refresh Token(用于刷新 JWT),就需要在服务器存储 Refresh Token,增加管理复杂度。 解决方案:
- 将 Refresh Token 存入 Redis,并设置一定的生命周期。
- 限制 Refresh Token 的使用次数,每次刷新 JWT 后生成新的 Refresh Token。 总结
| 问题 | 解决方案 |
|---|---|
| JWT 无法主动撤销 | 短生命周期 + Refresh Token + 黑名单 |
| JWT 体积大 | 只存必要数据,压缩优化 |
| JWT 不能修改 | 短生命周期,检查 Token 版本号 |
| JWT 容易泄露 | 使用 HTTPS,绑定 IP/设备 ID |
| JWT 不适用于所有场景 | 适用于无状态认证,复杂场景考虑 Session |
| Refresh Token 需要存储 | 使用 Redis 存储 Refresh Token |
JWT 适合的场景
✅ 适合:
- 移动端 / Web 前后端分离项目
- API 认证(比如 OAuth2)
- 需要无状态认证的服务
❌ 不适合:
- 需要高安全性、随时撤销 Token(如银行、企业后台管理系统)
- 需要频繁更新用户权限的系统”
六、token和JWT的区别
Token 是一种身份验证凭证,可以是任何格式的字符串。它用于标识用户身份或授权。 JWT(JSON Web Token) 是一种特定格式的 Token,由三部分组成:Header、Payload 和 Signature。JWT 自包含信息,并通过签名验证完整性,不需要服务器存储会话数据。
主要区别:
- Token:广义术语,可以是任何形式的凭证。
- JWT:一种基于 JSON 的 Token,具有固定结构和加密功能,适用于无状态应用。