JSON Web Tokens(JWT):身份验证指南
JWT 身份验证的完整指南。学习 JWT 结构、实现、安全最佳实践和令牌刷新策略。
Big JSON Team
• Technical WriterExpert in JSON data manipulation, API development, and web technologies. Passionate about creating tools that make developers' lives easier.
# JSON Web Tokens(JWT):身份验证指南
JWT(JSON Web Token)是一种紧凑、URL 安全的令牌格式,用于在各方之间安全地传输信息。它是现代 Web 应用中最流行的身份验证方式。
什么是 JWT?
JWT 是一个标准化的令牌格式(RFC 7519),用于:
- ✅ 身份验证 - 验证用户身份
- ✅ 授权 - 传递权限信息
- ✅ 信息交换 - 安全地传输声明
- ✅ 无状态 - 服务器无需存储会话
JWT 与传统会话
| 特性 | 会话 | JWT |
|------|------|-----|
| 存储 | 服务器 | 客户端 |
| 状态 | 有状态 | 无状态 |
| 扩展性 | 困难 | 容易 |
| 移动 | 需要 Cookie | 适合 API |
| 跨域 | 困难 | 简单 |
JWT 结构
JWT 由三部分组成,用点(.)分隔:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
[Header].[Payload].[Signature]
1. 标头(Header)
标头声明令牌的类型和签名算法。
{
"alg": "HS256",
"typ": "JWT"
}
然后使用 Base64 编码:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
常见算法:
| 算法 | 说明 | 安全性 | 用途 |
|------|------|--------|------|
| HS256 | HMAC SHA-256 | 中等 | 简单应用 |
| HS512 | HMAC SHA-512 | 高 | 敏感数据 |
| RS256 | RSA SHA-256 | 高 | 公钥基础 |
| ES256 | ECDSA SHA-256 | 高 | 现代应用 |
2. 有效负载(Payload)
有效负载包含声明(claims)。
{
"sub": "1234567890",
"name": "张三",
"email": "zhang@example.com",
"iat": 1516239022,
"exp": 1516242622
}
Base64 编码:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IuS6pOWTpSIsImVtYWlsIjoiemhhbmdAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjIsImV4cCI6MTUxNjI0MjYyMn0
标准声明:
| 声明 | 说明 | 示例 |
|------|------|------|
| sub | 主题(用户 ID) | "user_123" |
| iss | 发行者 | "https://example.com" |
| aud | 受众 | "app_name" |
| exp | 过期时间(Unix 时间戳) | 1516242622 |
| iat | 发行时间 | 1516239022 |
| nbf | 生效前 | 1516239022 |
| jti | JWT ID | "unique-id" |
{
"sub": "user_123",
"name": "张三",
"role": "admin",
"permissions": ["read", "write", "delete"],
"department": "工程部"
}
3. 签名(Signature)
签名使用标头中指定的算法对标头和有效负载进行签名。
签名过程:SIGNATURE = HMACSHA256(
BASE64(HEADER) + "." + BASE64(PAYLOAD),
SECRET
)
目的:
- ✅ 验证 - 确保令牌未被篡改
- ✅ 真实性 - 证明令牌来自可信来源
- ✅ 不可否认 - 发行者无法否认发行
JWT 工作流程
身份验证流程
1. 用户登录
输入:用户名和密码
↓
服务器验证凭证
↓
生成 JWT 令牌
↓
返回令牌给客户端
响应:{ "token": "eyJ..." }
↓
客户端存储令牌
存储位置:localStorage、sessionStorage 或 Cookie
↓
未来请求中包含令牌
标头:Authorization: Bearer eyJ...
↓
服务器验证令牌
检查签名和过期时间
↓
访问被允许
流程图
┌─────────────┐ ┌─────────────┐
│ 客户端 │ │ 服务器 │
└─────────────┘ └─────────────┘
│ │
│ 1. 发送凭证 (用户名/密码) │
├──────────────────────────────────>│
│ │
│ 2. 验证凭证 │
│ ├─┐
│ │ │
│ 3. 生成 JWT │<┘
│ │
│ 4. 返回 JWT 令牌 │
│<──────────────────────────────────┤
│ │
│ 5. 存储令牌 │
├─┐ │
│ │ │
│<┘ │
│ │
│ 6. 请求 + JWT (Authorization 标头)
├──────────────────────────────────>│
│ │
│ 7. 验证令牌 │
│ ├─┐
│ │ │
│ │<┘
│ 8. 返回受保护资源 │
│<──────────────────────────────────┤
│ │
Node.js 实现
使用 jsonwebtoken 库
安装:npm install jsonwebtoken dotenv express
基本设置:
import jwt from 'jsonwebtoken';
import express from 'express';
const app = express();
const SECRET = process.env.JWT_SECRET || 'your-secret-key';
// 生成令牌
function generateToken(user) {
const payload = {
id: user.id,
email: user.email,
role: user.role
};
return jwt.sign(payload, SECRET, {
expiresIn: '1h', // 1 小时后过期
algorithm: 'HS256'
});
}
// 验证令牌
function verifyToken(token) {
try {
const decoded = jwt.verify(token, SECRET);
return { valid: true, data: decoded };
} catch (error) {
return { valid: false, error: error.message };
}
}
// 中间件:保护路由
function protectRoute(req, res, next) {
const authHeader = req.headers.authorization;
const token = authHeader?.split(' ')[1]; // "Bearer TOKEN"
if (!token) {
return res.status(401).json({ error: '缺少令牌' });
}
const result = verifyToken(token);
if (!result.valid) {
return res.status(401).json({ error: '无效令牌', details: result.error });
}
req.user = result.data;
next();
}
// 登录路由
app.post('/login', (req, res) => {
const user = { id: 1, email: 'user@example.com', role: 'user' };
const token = generateToken(user);
res.json({ token });
});
// 受保护的路由
app.get('/profile', protectRoute, (req, res) => {
res.json({ user: req.user });
});
app.listen(3000);
令牌刷新
// 生成访问令牌和刷新令牌
function generateTokens(user) {
const accessToken = jwt.sign(user, SECRET, {
expiresIn: '15m' // 短期
});
const refreshToken = jwt.sign(user, REFRESH_SECRET, {
expiresIn: '7d' // 长期
});
return { accessToken, refreshToken };
}
// 刷新路由
app.post('/refresh', (req, res) => {
const { refreshToken } = req.body;
try {
const user = jwt.verify(refreshToken, REFRESH_SECRET);
const accessToken = jwt.sign(user, SECRET, { expiresIn: '15m' });
res.json({ accessToken });
} catch (error) {
res.status(401).json({ error: '刷新令牌无效' });
}
});
Python 实现
安装:pip install PyJWT
实现:
import jwt
from datetime import datetime, timedelta
import os
SECRET = os.environ.get('JWT_SECRET', 'your-secret-key')
def generate_token(user):
payload = {
'id': user['id'],
'email': user['email'],
'iat': datetime.utcnow(),
'exp': datetime.utcnow() + timedelta(hours=1)
}
return jwt.encode(payload, SECRET, algorithm='HS256')
def verify_token(token):
try:
decoded = jwt.decode(token, SECRET, algorithms=['HS256'])
return {'valid': True, 'data': decoded}
except jwt.ExpiredSignatureError:
return {'valid': False, 'error': '令牌已过期'}
except jwt.InvalidTokenError:
return {'valid': False, 'error': '无效令牌'}
# Flask 中使用
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/login', methods=['POST'])
def login():
user = {'id': 1, 'email': 'user@example.com'}
token = generate_token(user)
return jsonify({'token': token})
@app.route('/profile', methods=['GET'])
def profile():
auth_header = request.headers.get('Authorization')
if not auth_header:
return jsonify({'error': '缺少授权头'}), 401
token = auth_header.split(' ')[1]
result = verify_token(token)
if not result['valid']:
return jsonify({'error': result['error']}), 401
return jsonify({'user': result['data']})
if __name__ == '__main__':
app.run(debug=True)
安全最佳实践
1. 使用 HTTPS
// ✓ 正确:HTTPS 上的令牌
app.use((req, res, next) => {
if (!req.secure && process.env.NODE_ENV === 'production') {
return res.redirect('https://' + req.headers.host + req.url);
}
next();
});
2. 保护密钥
# .env 文件(不要提交到 git)
JWT_SECRET=your-super-secret-key-minimum-32-characters-long
REFRESH_SECRET=another-secret-key-for-refresh-tokens
# .gitignore
.env
.env.local
3. 短过期时间
// 访问令牌:15 分钟
const accessToken = jwt.sign(payload, SECRET, { expiresIn: '15m' });
// 刷新令牌:7 天
const refreshToken = jwt.sign(payload, REFRESH_SECRET, { expiresIn: '7d' });
4. 验证声明
function verifyToken(token, expectedAudience) {
try {
const decoded = jwt.verify(token, SECRET);
// 检查必要的声明
if (decoded.aud !== expectedAudience) {
throw new Error('受众不匹配');
}
if (!decoded.sub) {
throw new Error('缺少主题');
}
return decoded;
} catch (error) {
return null;
}
}
5. 令牌撤销
// 简单的黑名单(使用 Redis 更好)
const tokenBlacklist = new Set();
function logout(token) {
tokenBlacklist.add(token);
}
function verifyToken(token) {
if (tokenBlacklist.has(token)) {
throw new Error('令牌已被撤销');
}
return jwt.verify(token, SECRET);
}
// 使用 Redis 的更好方式
import redis from 'redis';
const client = redis.createClient();
async function logout(token) {
const decoded = jwt.decode(token);
const expiresIn = decoded.exp - Math.floor(Date.now() / 1000);
await client.setex(blacklist:${token}, expiresIn, 'true');
}
async function isBlacklisted(token) {
return await client.exists(blacklist:${token});
}
常见攻击和防护
| 攻击 | 描述 | 防护 |
|------|------|------|
| 令牌盗窃 | XSS 攻击获取令牌 | 使用 HttpOnly Cookie、CSP |
| 令牌篡改 | 修改有效负载 | 验证签名 |
| 重放攻击 | 重用旧令牌 | 短过期时间、令牌撤销 |
| 密钥泄露 | 密钥被暴露 | 定期轮换、强密钥 |
| Jti 重复 | 重复令牌 ID | 检查 jti 唯一性 |
JWT 调试
解码 JWT(不验证)
const token = 'eyJ...';
const decoded = jwt.decode(token); // 不验证签名!
console.log(decoded);
在线工具
访问 https://jwt.io 来检查和调试 JWT:
日志和监控
function verifyToken(token) {
try {
const decoded = jwt.verify(token, SECRET);
console.log('✓ 令牌有效', decoded);
return decoded;
} catch (error) {
console.error('✗ 令牌错误', error.message);
return null;
}
}
结论
JWT 是现代 Web 应用的标准身份验证方法:
- ✅ 无状态 - 服务器不需要存储会话
- ✅ 可扩展 - 适用于分布式系统
- ✅ 安全 - 使用签名和加密
- ✅ 灵活 - 支持自定义声明
遵循最佳实践,确保应用程序安全!
相关文章
什么是 JSON?2026 年初学者完整指南
了解 JSON 是什么,它的语法、数据类型和使用场景。全面且对初学者友好的 JavaScript 对象表示法指南。
JavaScript JSON:解析和字符串化数据
JavaScript 中 JSON 的完整指南。学习 JSON.parse()、JSON.stringify()、错误处理和 Web 开发的高级技术。
JSON API:构建和使用 REST 服务
学习构建和使用基于 JSON 的 REST API。涵盖 HTTP 方法、身份验证、最佳实践和实际实现示例。