本文目录导读:

JWT(JSON Web Token)是实现无状态认证的主流方案,所谓“无状态”,是指服务端不需要存储会话信息(如 Session),所有用户身份信息都编码在 Token 中,服务端只需验证 Token 的签名即可。
以下是使用 JWT 实现无状态认证的完整流程、代码示例和最佳实践。
JWT 的结构
一个 JWT 由三部分组成,用 分隔:
header.payload.signature
- Header:声明类型和签名算法(如 HS256、RS256)。
- Payload:存放用户信息(如 userId、role)和声明(如过期时间
exp)。 - Signature:对前两部分进行签名,防止篡改。
无状态认证流程
sequenceDiagram
participant Client as 客户端
participant Server as 服务端
Client->>Server: 1. POST /login (用户名+密码)
Server->>Server: 2. 验证密码,生成JWT
Server-->>Client: 3. 返回JWT
Client->>Server: 4. 请求API + Authorization: Bearer <JWT>
Server->>Server: 5. 验证JWT签名和过期时间
Server-->>Client: 6. 返回资源
关键点:
- 服务端不存 Session,只依赖 JWT 中的信息。
- JWT 由服务端签发,客户端保存在 localStorage 或 Cookie 中。
- 每次请求携带 JWT,服务端验证签名后即可识别用户。
具体实现(Node.js + Express 示例)
安装依赖
npm install jsonwebtoken express
登录接口(签发 JWT)
const jwt = require('jsonwebtoken');
const express = require('express');
const app = express();
app.use(express.json());
// 模拟用户数据库(实际应查 DB)
const users = [{ id: 1, username: 'alice', password: '123456' }];
// 密钥(生产环境用环境变量)
const SECRET_KEY = 'your-256-bit-secret';
app.post('/login', (req, res) => {
const { username, password } = req.body;
const user = users.find(u => u.username === username && u.password === password);
if (!user) return res.status(401).json({ message: '认证失败' });
// 生成 JWT,payload 只存必要信息
const token = jwt.sign(
{
userId: user.id,
role: 'user' // 可以放角色等
},
SECRET_KEY,
{ expiresIn: '1h' } // 设置过期时间
);
res.json({ token });
});
认证中间件(验证 JWT)
const authMiddleware = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer <token>
if (!token) return res.status(401).json({ message: '未提供认证令牌' });
try {
const decoded = jwt.verify(token, SECRET_KEY);
req.user = decoded; // 将用户信息注入请求对象
next();
} catch (err) {
// token 过期或无效
return res.status(401).json({ message: '令牌无效或已过期' });
}
};
// 受保护路由
app.get('/profile', authMiddleware, (req, res) => {
res.json({
userId: req.user.userId,
message: '这是受保护的资源'
});
});
客户端使用(前端示例)
// 登录成功后存储 token
fetch('/login', { method: 'POST', body: JSON.stringify({ username, password }) })
.then(res => res.json())
.then(data => {
localStorage.setItem('token', data.token);
});
// 请求携带 token
fetch('/profile', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
关键注意事项
安全存储 Token
- 前端:优先放在
httpOnlyCookie 中(可防 XSS 攻击),localStorage。 - 后端:不要在 JWT 中存放敏感信息(如密码),payload 是 Base64 编码,可被解码。
签名算法选择
- HS256(对称加密):简单,但密钥需保密,适合单体应用。
- RS256(非对称加密):使用公钥/私钥对,私钥由认证服务持有,其他服务可用公钥验证,适合微服务架构。
过期与刷新
- 短期 Token:如 15 分钟,减少泄露风险。
- Refresh Token:长期有效的令牌(如 7 天),用于获取新的 Access Token。
// 刷新接口示例 app.post('/refresh', (req, res) => { const refreshToken = req.body.refreshToken; // 验证 refreshToken(可以存数据库或使用另一个密钥签名) // 返回新的 accessToken });
注销问题
- 无状态 Token 一旦签发,在过期前无法主动失效。
- 解决方案:
- 黑名单机制:维护一个撤销列表(有状态,但仅存储已撤销的 Token)。
- 短期 Token + Refresh Token:Refresh Token 被撤销,强制用户重新登录。
性能与优化
- 每次验证需解密并校验签名,比 Session 的简单查内存慢。
- 可使用缓存(如 Redis)缓存公钥或验证结果。
完整代码仓库示例(FastAPI + Python 版本)
from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt
app = FastAPI()
security = HTTPBearer()
SECRET_KEY = "your-secret-key"
# 创建 JWT
@app.post("/login")
def login(username: str, password: str):
user = {"id": 1, "username": "alice"} # 实际需验证密码
token = jwt.encode({"userId": user["id"], "exp": 3600}, SECRET_KEY, algorithm="HS256")
return {"access_token": token}
# 验证中间件
def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
try:
payload = jwt.decode(credentials.credentials, SECRET_KEY, algorithms=["HS256"])
return payload
except jwt.ExpiredSignatureError:
raise HTTPException(401, "Token expired")
except jwt.InvalidTokenError:
raise HTTPException(401, "Invalid token")
@app.get("/protected")
def protected_route(user=Depends(verify_token)):
return {"user_id": user["userId"]}
| 特性 | 传统 Session | JWT 无状态 |
|---|---|---|
| 存储 | 服务端内存/Redis | 客户端存储 Token |
| 扩展性 | 需共享 Session 存储 | 天然支持跨服务 |
| 注销 | 立即删除 Session | 需额外机制 |
| 性能 | 查存储 O(1) | 解密验证 O(n) |
最佳实践:
- 使用
RS256签名(微服务场景)。 - Token 不要超过 8KB(避免 HTTP 头过大)。
- 敏感操作(支付、改密)需额外验证(如 OTP)。
- 始终使用 HTTPS 传输。
JWT 的无状态特性使其特别适合分布式系统、移动端和前后端分离场景,但需要配合刷新令牌和黑名单机制解决注销和续期问题。