本文目录导读:
核心概念:JWT 是什么?
JWT 本质上是一个令牌,它由三部分组成,用点号()连接:
Header.Payload.Signature
- Header(头部):声明了令牌的类型(JWT)和使用的签名算法(如 HS256)。
- Payload(负载):存放有效信息,比如用户ID、过期时间等。注意:这里的数据是经过 Base64 编码的,不是加密的,所以不要放敏感信息。
- Signature(签名):由 Header、Payload 和一个秘钥通过指定算法生成,用来验证令牌是否被篡改。
简单理解,JWT 是一个自包含的令牌——服务端不需要存储会话信息,只需要验证签名就能确认用户身份。
完整使用流程
一次典型的 JWT 认证流程如下:
- 用户登录:客户端(如浏览器、App)发送用户名和密码到服务端。
- 服务端验证:服务端验证用户名密码正确后,生成一个 JWT 并返回给客户端。
- 客户端存储:客户端将 JWT 保存起来(通常是 localStorage 或 HTTP-only Cookie)。
- 后续请求:客户端在每次需要认证的请求中,把 JWT 附加在 HTTP 请求头的
Authorization字段里,格式通常是Bearer <token>。 - 服务端验证:服务端收到请求后,从 Authorization 头取出 JWT,验证签名和有效性(如过期时间),如果有效则放行。
代码示例(Node.js + express)
我用 Node.js 最常用的 jsonwebtoken 库来演示,其他语言原理完全一样。
安装依赖
npm install jsonwebtoken express
服务端代码
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
const SECRET_KEY = 'your-secret-key'; // 生产环境应该使用更复杂的秘钥,并妥善保管
// 模拟用户数据
const users = {
'admin': { password: '123456', role: 'admin' }
};
app.use(express.json());
// 登录接口:验证成功后返回 JWT
app.post('/login', (req, res) => {
const { username, password } = req.body;
const user = users[username];
if (!user || user.password !== password) {
return res.status(401).json({ message: '用户名或密码错误' });
}
// 生成 JWT
const token = jwt.sign(
{
username: username,
role: user.role
},
SECRET_KEY,
{ expiresIn: '1h' } // 令牌有效期:1小时
);
res.json({ token: token });
});
// 需要认证的接口示例
app.get('/protected', (req, res) => {
const authHeader = req.headers['authorization'];
if (!authHeader) {
return res.status(401).json({ message: '未提供认证令牌' });
}
// 提取 token(格式:"Bearer <token>")
const token = authHeader.split(' ')[1];
try {
// 验证并解码 JWT
const decoded = jwt.verify(token, SECRET_KEY);
// 验证通过,可以在此使用 decoded 中的用户信息
res.json({
message: '访问成功',
user: decoded
});
} catch (err) {
// 验证失败(令牌过期、签名错误等)
return res.status(403).json({ message: '令牌无效或已过期' });
}
});
app.listen(3000, () => console.log('服务已启动'));
客户端测试(使用 curl)
# 1. 登录获取 token
curl -X POST http://localhost:3000/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"123456"}'
# 返回:{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}
# 2. 使用 token 访问受保护接口
curl http://localhost:3000/protected \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
关键注意事项
-
秘钥安全:
SECRET_KEY必须保密,绝不能硬编码在代码里或提交到 Git,建议使用环境变量(process.env.JWT_SECRET)。 -
令牌存储:
- 如果存
localStorage,要注意 XSS 攻击(攻击者通过脚本读取到 token)。 - 推荐使用 HTTP-only Cookie 存储 JWT,这样 JavaScript 无法直接读取,防范 XSS 更有效。
- 如果存
-
令牌过期:一定要设置
expiresIn,业务需要长期登录时,可以考虑双 Token 机制:- Access Token:短期(如 15 分钟),用于正常请求。
- Refresh Token:长期(如 7 天),用于在 Access Token 过期后获取新的 Access Token,且存储在更安全的地方。
-
无法撤销:JWT 一旦签发,在其有效期内都是有效的(除非你额外实现黑名单机制),如果想主动让一个用户下线,需要维护一个 token 黑名单或使用短期 token 配合 refresh token。
-
Payload 不要放敏感信息:PayLoad是 Base64 编码,不是加密,可以被轻松解码看到,如果需要在令牌里携带敏感数据,请额外加密。
-
使用 HTTPS:JWT 通过网络传输,务必使用 HTTPS 防止中间人窃取。
常见问题
Q:JWT 和 Session 的主要区别是什么?
- Session:服务端存储会话信息,客户端只存一个 Session ID,适合有状态的应用。
- JWT:客户端存储令牌,服务端无状态(不存会话),适合分布式系统和移动应用。
Q:如何让 JWT 失效?
JWT 本身没有服务端撤销机制,你可以:
- 设置非常短的过期时间。
- 维护一个“黑名单”数据库/缓存,每次请求时检查。
- 使用双 Token 方案,让 Refresh Token 可撤销。
如果你想了解更多,比如如何在特定框架(如 Spring Boot、Django、Go)中使用 JWT,或者想深入了解双 Token 机制的具体实现,可以告诉我,我可以补充更具体的示例。