Dev
JWT完全指南2026 - 结构、安全性和最佳实践总结
从JSON Web Token的结构和工作原理到安全漏洞和最佳实践。通过实战代码示例掌握JWT认证。
Toolypet Team
Development Team
JWT完全指南2026
"JWT?不就是个令牌吗?"
没错。但是如果这个"令牌"实现不当,就会成为严重的安全问题。截至2026年,研究表明48%的AI生成认证代码存在安全漏洞。
本指南将完整介绍从JWT结构到实战安全最佳实践的所有内容。
什么是JWT?
JWT(JSON Web Token)是一种紧凑且自包含的方式,用于在两方之间安全地传输信息。
主要特点
| 特点 | 说明 |
|---|---|
| Stateless | 服务器无需存储会话 |
| Self-contained | 所需信息包含在令牌内 |
| Compact | 可用于URL、HTTP头 |
| Signed | 可检测篡改 |
使用场景
- API认证:使用Bearer令牌进行API请求
- SSO(单点登录):多服务间共享认证
- 信息交换:传输签名数据
- Authorization:包含权限/角色信息
JWT结构
JWT由三个用.(点)分隔的Base64URL编码部分组成。
xxxxx.yyyyy.zzzzz
| | |
Header Payload Signature
1. Header
{
"alg": "HS256",
"typ": "JWT"
}
| 字段 | 说明 |
|---|---|
alg | 签名算法(HS256、RS256等) |
typ | 令牌类型(JWT) |
2. Payload (Claims)
{
"sub": "user123",
"name": "张三",
"email": "zhang@example.com",
"role": "admin",
"iat": 1708502400,
"exp": 1708588800
}
注册声明(Registered Claims)
| 声明 | 说明 | 示例 |
|---|---|---|
iss | 签发者(Issuer) | "https://auth.example.com" |
sub | 主题(Subject) | "user123" |
aud | 受众(Audience) | "https://api.example.com" |
exp | 过期时间(Expiration) | 1708588800 |
nbf | 生效时间(Not Before) | 1708502400 |
iat | 签发时间(Issued At) | 1708502400 |
jti | JWT唯一ID | "abc123" |
公共/私有声明
{
"role": "admin", // 私有声明
"email": "user@example.com",
"permissions": ["read", "write"]
}
3. Signature
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
签名算法
对称密钥(Symmetric)
| 算法 | 说明 | 使用场景 |
|---|---|---|
| HS256 | HMAC + SHA-256 | 单服务器、简单实现 |
| HS384 | HMAC + SHA-384 | 更强的安全性 |
| HS512 | HMAC + SHA-512 | 最高级别安全性 |
// Node.js - HS256
const jwt = require('jsonwebtoken');
const token = jwt.sign(
{ userId: 123, role: 'admin' },
'your-256-bit-secret', // 使用相同密钥验证
{ algorithm: 'HS256', expiresIn: '1h' }
);
非对称密钥(Asymmetric)
| 算法 | 说明 | 使用场景 |
|---|---|---|
| RS256 | RSA + SHA-256 | 微服务、公开验证 |
| RS384 | RSA + SHA-384 | 高安全要求 |
| RS512 | RSA + SHA-512 | 最高级别安全性 |
| ES256 | ECDSA + P-256 | 短密钥、移动端 |
// RS256 - 非对称
const privateKey = fs.readFileSync('private.pem');
const publicKey = fs.readFileSync('public.pem');
// 签发(Private Key)
const token = jwt.sign(payload, privateKey, { algorithm: 'RS256' });
// 验证(Public Key)
const decoded = jwt.verify(token, publicKey, { algorithms: ['RS256'] });
算法选择指南
| 场景 | 推荐算法 |
|---|---|
| 单服务器 | HS256 |
| 微服务 | RS256 |
| 需要公开验证 | RS256 / ES256 |
| 移动端/IoT | ES256(短密钥) |
实战实现
Node.js (Express)
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
const SECRET = process.env.JWT_SECRET; // 从环境变量加载
// 签发令牌
app.post('/login', async (req, res) => {
const { email, password } = req.body;
// 用户验证(示例)
const user = await verifyCredentials(email, password);
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const token = jwt.sign(
{
sub: user.id,
email: user.email,
role: user.role,
},
SECRET,
{
expiresIn: '1h',
issuer: 'your-app-name',
}
);
res.json({ token });
});
// 中间件验证
const authMiddleware = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing token' });
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, SECRET, {
issuer: 'your-app-name',
algorithms: ['HS256'],
});
req.user = decoded;
next();
} catch (err) {
return res.status(401).json({ error: 'Invalid token' });
}
};
// 受保护的路由
app.get('/protected', authMiddleware, (req, res) => {
res.json({ message: `Hello, ${req.user.email}` });
});
Python (FastAPI)
from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt
from datetime import datetime, timedelta
import os
app = FastAPI()
security = HTTPBearer()
SECRET = os.environ.get("JWT_SECRET")
# 签发令牌
def create_token(user_id: str, role: str) -> str:
payload = {
"sub": user_id,
"role": role,
"iat": datetime.utcnow(),
"exp": datetime.utcnow() + timedelta(hours=1),
}
return jwt.encode(payload, SECRET, algorithm="HS256")
# 验证令牌
def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
token = credentials.credentials
try:
payload = jwt.decode(token, SECRET, algorithms=["HS256"])
return payload
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail="Token expired")
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="Invalid token")
# 受保护的端点
@app.get("/protected")
def protected_route(user: dict = Depends(verify_token)):
return {"message": f"Hello, {user['sub']}"}
安全漏洞与对策
1. Algorithm None攻击
// 恶意header
{
"alg": "none",
"typ": "JWT"
}
对策:
// 明确指定允许的算法
jwt.verify(token, secret, { algorithms: ['HS256'] }); // 正确
jwt.verify(token, secret); // 危险
2. 弱密钥
// 危险
const SECRET = 'secret123';
// 安全(至少256位)
const SECRET = crypto.randomBytes(32).toString('hex');
3. 敏感信息泄露
// 危险:Payload可被Base64解码
{
"password": "user_password", // 绝对禁止!
"creditCard": "1234-5678-9012-3456"
}
// 安全
{
"sub": "user123",
"role": "admin"
}
4. 令牌盗用
应对策略:
- 短过期时间(15分钟~1小时)
- 使用Refresh Token
- HTTPS必须
- HttpOnly Cookie(防XSS)
5. JWT重放攻击(Replay Attack)
// 使用jti(JWT ID)
const token = jwt.sign({
sub: user.id,
jti: crypto.randomUUID(), // 唯一ID
}, SECRET);
// 服务器管理jti黑名单
Access Token + Refresh Token
为什么需要?
| 令牌 | 有效期 | 用途 |
|---|---|---|
| Access Token | 15分钟~1小时 | API认证 |
| Refresh Token | 7天~30天 | 刷新Access Token |
实现示例
// 登录时签发两个令牌
app.post('/login', async (req, res) => {
const user = await verifyCredentials(req.body);
const accessToken = jwt.sign(
{ sub: user.id, type: 'access' },
ACCESS_SECRET,
{ expiresIn: '15m' }
);
const refreshToken = jwt.sign(
{ sub: user.id, type: 'refresh' },
REFRESH_SECRET,
{ expiresIn: '7d' }
);
// Refresh Token使用HttpOnly Cookie
res.cookie('refreshToken', refreshToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 7 * 24 * 60 * 60 * 1000,
});
res.json({ accessToken });
});
// 刷新令牌
app.post('/refresh', (req, res) => {
const refreshToken = req.cookies.refreshToken;
try {
const decoded = jwt.verify(refreshToken, REFRESH_SECRET);
const newAccessToken = jwt.sign(
{ sub: decoded.sub, type: 'access' },
ACCESS_SECRET,
{ expiresIn: '15m' }
);
res.json({ accessToken: newAccessToken });
} catch (err) {
res.status(401).json({ error: 'Invalid refresh token' });
}
});
JWT调试
令牌解码
在JWT Decoder中粘贴令牌可以:
- 查看Header
- 查看Payload
- 查看过期时间
- 验证签名(输入密钥时)
CLI解码
# 解码Header
echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" | base64 -d
# 解码Payload
echo "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4ifQ" | base64 -d
最佳实践清单
签发
- 强密钥(256位以上)
- 明确算法(HS256、RS256)
- 短过期时间(15分钟~1小时)
- 包含必要声明(sub、iat、exp、iss)
- 不包含敏感信息
验证
- 算法白名单(
algorithms: ['HS256']) - 过期时间验证
- 签发者(iss)验证
- 受众(aud)验证
存储与传输
- HTTPS必须
- HttpOnly Cookie(防XSS)
- SameSite属性(防CSRF)
- 避免localStorage(XSS脆弱)
常见问题
Q1: JWT vs Session,何时使用哪个?
A:
- JWT:无状态API、微服务、移动应用
- Session:传统Web应用、服务端渲染、需要即时登出
Q2: JWT被盗怎么办?
A:Access Token在过期前有效。因此:
- 设置短过期时间(15分钟)
- Refresh Token Rotation
- 怀疑时使所有令牌失效
Q3: 为什么不应该存储在localStorage?
A:容易受到XSS攻击。由于JavaScript可以访问,恶意脚本可能窃取令牌。HttpOnly Cookie更安全。
Q4: RS256比HS256更安全吗?
A:不是"更安全"而是"用途不同"。
- HS256:共享相同密钥、单服务器
- RS256:用公钥验证、多服务
Q5: 需要令牌黑名单吗?
A:如果需要即时登出则需要。但这会减少无状态的优势。短过期时间 + Refresh Token是替代方案。
总结
JWT安全要点:
- 强密钥:256位以上随机
- 算法验证:白名单方式
- 短有效期:Access 15分钟、Refresh 7天
- 安全存储:HttpOnly + Secure Cookie
- 排除敏感信息:Payload任何人都能解码
相关工具
| 工具 | 用途 |
|---|---|
| JWT Decoder | JWT解码和验证 |
| Base64 Encoder | Base64编码/解码 |
| Hash Generator | 哈希生成 |
JWT认证安全APIWeb开发令牌
关于作者
Toolypet Team
Development Team
The Toolypet Team creates free, privacy-focused web tools for developers and designers. All tools run entirely in your browser with no data sent to servers.
Web DevelopmentCSS ToolsDeveloper ToolsSEOSecurity