JWT
JSON Web Token (JWT) 是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间以 JSON 对象的形式安全地传输信息。由于信息是经过数字签名的,所以可以验证其完整性。另外,可以对 JWT 进行加密以保护数据的机密性。
JWT 主要用于身份验证和信息交换,特别适合用于分布式站点的单点登录(SSO)场景。

查看更多相关内容
JWT 和 Session 认证有什么区别JWT (JSON Web Token) 和 Session 认证是两种常见的身份验证机制,它们有以下主要区别:
## 1. 存储位置
- **JWT**: 存储在客户端(通常是 LocalStorage 或 Cookie),每次请求时通过 HTTP Header 传递
- **Session**: 存储在服务器端(内存、Redis、数据库等),客户端只保存 Session ID(通常在 Cookie 中)
## 2. 无状态 vs 有状态
- **JWT**: 无状态,服务器不需要存储会话信息,每个请求都包含所有必要信息
- **Session**: 有状态,服务器需要维护会话存储,占用服务器内存
## 3. 可扩展性
- **JWT**: 易于水平扩展,任何服务器都可以验证 JWT,无需共享会话状态
- **Session**: 扩展性较差,多台服务器需要共享会话存储(如 Redis 集群)
## 4. 安全性
- **JWT**:
- 优点:可以设置过期时间,使用 HTTPS 传输
- 缺点:一旦签发无法主动撤销,泄露后直到过期前都有效
- **Session**:
- 优点:可以主动销毁会话,安全性更高
- 缺点:Session ID 可能被劫持(CSRF 攻击)
## 5. 数据大小
- **JWT**: 包含用户信息,token 较大(通常 1-2KB)
- **Session**: 只存储 Session ID,较小(几十字节)
## 6. 跨域支持
- **JWT**: 天然支持跨域,适合移动端和分布式系统
- **Session**: 需要处理跨域 Cookie 问题,配置较复杂
## 7. 性能
- **JWT**: 每次请求都需要验证签名,计算开销较大
- **Session**: 只需查找 Session ID,性能较好
## 适用场景
**使用 JWT 的场景**:
- 分布式系统和微服务架构
- 移动应用和单页应用(SPA)
- 需要跨域访问的 API
- 第三方授权(OAuth2.0)
**使用 Session 的场景**:
- 传统 Web 应用
- 需要实时撤销权限的场景
- 对安全性要求极高的系统
- 用户量相对较小的应用
## 最佳实践
- 可以结合使用:JWT 用于 API 认证,Session 用于管理后台
- 使用 JWT 时务必配合 HTTPS
- 设置合理的过期时间
- 敏感操作需要二次验证
服务端 · 2026年2月21日 17:53
什么是 JWT,它由哪几部分组成JWT (JSON Web Token) 是一种开放标准 (RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间以 JSON 对象的形式安全地传输信息。JWT 由三个部分组成,通过点(.)分隔:
1. **Header (头部)**:包含两部分信息:令牌类型(通常是 JWT)和使用的签名算法(如 HS256、RS256 等)。Header 是一个 JSON 对象,经过 Base64Url 编码。
2. **Payload (载荷)**:包含声明(Claims),即关于实体(通常是用户)和其他数据的声明。声明分为三类:
- 注册声明(Registered Claims):如 iss(签发者)、exp(过期时间)、sub(主题)、aud(受众)等
- 公共声明(Public Claims):可以自定义,但应避免冲突
- 私有声明(Private Claims):在同意使用它们的各方之间共享信息的自定义声明
Payload 也是 JSON 对象,经过 Base64Url 编码。
3. **Signature (签名)**:用于验证消息在传递过程中未被篡改。签名是通过将编码后的 Header 和 Payload 用点连接,然后使用 Header 中指定的算法和密钥进行签名生成的。
JWT 的完整格式为:`Header.Payload.Signature`
例如:
```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
```
JWT 的主要特点:
- 紧凑:可以通过 URL、POST 参数或 HTTP header 发送
- 自包含:包含所有必要信息,减少数据库查询
- 跨语言支持:多种编程语言都有实现
- 无状态:服务器不需要存储会话信息
JWT 常用于身份验证和信息交换,特别适合分布式系统的单点登录(SSO)场景。
服务端 · 2026年2月21日 17:53
JWT 和 OAuth2.0 有什么区别JWT 和 OAuth2.0 是两个不同的概念,经常被混淆。以下是它们的区别和关系:
## JWT (JSON Web Token)
### 定义
JWT 是一种**令牌格式标准**(RFC 7519),定义了一种紧凑且自包含的方式,用于在各方之间以 JSON 对象的形式安全地传输信息。
### 特点
- **格式标准**: 定义了 token 的结构和编码方式
- **自包含**: 包含所有必要信息,无需查询数据库
- **无状态**: 服务器不需要存储会话信息
- **跨语言**: 多种编程语言都有实现
### 用途
- 身份验证
- 信息交换
- 单点登录(SSO)
### 示例
```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
```
## OAuth2.0
### 定义
OAuth2.0 是一个**授权框架**(RFC 6749),定义了授权流程和规范,允许用户授权第三方应用访问其在另一个服务上的资源,而无需共享密码。
### 特点
- **授权框架**: 定义了授权流程和角色
- **委托授权**: 用户授权第三方应用访问资源
- **令牌机制**: 使用 Access Token 访问资源
- **多种授权模式**: 支持授权码、隐式、密码、客户端凭证等模式
### 角色
- **Resource Owner**: 资源所有者(用户)
- **Client**: 第三方应用
- **Authorization Server**: 授权服务器
- **Resource Server**: 资源服务器
### 授权模式
1. **Authorization Code**: 最安全,推荐用于有后端的应用
2. **Implicit**: 简单但不安全,用于纯前端应用
3. **Resource Owner Password Credentials**: 用户名密码,信任的应用
4. **Client Credentials**: 服务对服务,不需要用户参与
### 示例流程
```
用户 → 第三方应用 → 授权服务器 → 资源服务器
```
## JWT 与 OAuth2.0 的关系
### 关键点
**JWT 是一种令牌格式,OAuth2.0 是一个授权框架**
### 它们可以结合使用
- OAuth2.0 可以使用 JWT 作为 Access Token 的格式
- OAuth2.0 的授权流程可以返回 JWT 格式的 token
- JWT 可以携带 OAuth2.0 的声明信息
### 示例: OAuth2.0 使用 JWT
```javascript
// OAuth2.0 授权流程
// 1. 用户授权
GET /authorize?
response_type=code&
client_id=CLIENT_ID&
redirect_uri=REDIRECT_URI&
scope=read%20write
// 2. 获取 Access Token (JWT 格式)
POST /token
{
"grant_type": "authorization_code",
"code": "AUTHORIZATION_CODE",
"client_id": "CLIENT_ID",
"client_secret": "CLIENT_SECRET"
}
// 响应 (JWT 格式的 Access Token)
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "REFRESH_TOKEN"
}
// 3. 使用 JWT Access Token 访问资源
GET /api/resource
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
```
## 对比总结
| 特性 | JWT | OAuth2.0 |
|------|-----|----------|
| 类型 | 令牌格式标准 | 授权框架 |
| 目的 | 安全传输信息 | 授权第三方访问资源 |
| 状态 | 无状态 | 可以有状态 |
| 存储 | 客户端存储 | 服务器可存储 |
| 撤销 | 困难 | 较容易 |
| 标准化 | RFC 7519 | RFC 6749 |
| 独立性 | 可独立使用 | 需要配合使用 |
## 实际应用场景
### 1. 使用 JWT 的场景
```javascript
// 简单的身份验证
const token = jwt.sign({ userId: '123' }, SECRET_KEY);
// 验证
const decoded = jwt.verify(token, SECRET_KEY);
```
### 2. 使用 OAuth2.0 的场景
```javascript
// 第三方登录(如 Google、GitHub)
app.get('/auth/google', passport.authenticate('google'));
app.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '/login' }),
(req, res) => {
res.redirect('/');
}
);
```
### 3. 结合使用 JWT 和 OAuth2.0
```javascript
// OAuth2.0 授权服务器签发 JWT
app.post('/oauth/token', (req, res) => {
const { grant_type, code } = req.body;
// 验证授权码
const authCode = await validateAuthCode(code);
// 生成 JWT 格式的 Access Token
const accessToken = jwt.sign({
sub: authCode.userId,
scope: authCode.scope,
client_id: authCode.clientId
}, PRIVATE_KEY, {
algorithm: 'RS256',
expiresIn: '1h'
});
res.json({
access_token: accessToken,
token_type: 'Bearer',
expires_in: 3600
});
});
// 资源服务器验证 JWT
app.get('/api/resource', async (req, res) => {
const token = req.headers['authorization']?.replace('Bearer ', '');
try {
const decoded = jwt.verify(token, PUBLIC_KEY, {
algorithms: ['RS256'],
issuer: 'https://auth.example.com'
});
// 检查 scope
if (!decoded.scope.includes('read')) {
return res.status(403).json({ error: 'Insufficient scope' });
}
res.json({ data: 'protected resource' });
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
});
```
## 选择建议
### 选择 JWT 的情况
- 简单的身份验证需求
- 内部系统通信
- 不需要复杂的授权流程
- 单点登录(SSO)
### 选择 OAuth2.0 的情况
- 需要第三方授权
- 用户需要控制权限
- 需要多种授权模式
- 企业级应用
### 结合使用的情况
- 需要 OAuth2.0 的授权流程
- 需要 JWT 的自包含特性
- 需要跨服务验证 token
- 需要细粒度的权限控制
## 常见误区
### 误区 1: JWT 可以替代 OAuth2.0
**错误**: JWT 是令牌格式,OAuth2.0 是授权框架,它们是不同层面的概念。
### 误区 2: OAuth2.0 必须使用 JWT
**错误**: OAuth2.0 可以使用任意格式的 token,不一定是 JWT。
### 误区 3: JWT 更安全
**错误**: 安全性取决于实现方式,JWT 和 OAuth2.0 都可以很安全,也可能不安全。
### 误区 4: OAuth2.0 必须很复杂
**错误**: OAuth2.0 可以根据需求选择合适的授权模式,不一定复杂。
## 最佳实践
1. **理解需求**: 明确是需要令牌格式还是授权框架
2. **选择合适的技术**: 根据场景选择 JWT、OAuth2.0 或两者结合
3. **安全实现**: 无论使用哪种技术,都要注意安全
4. **遵循标准**: 遵循 RFC 标准和最佳实践
5. **测试验证**: 充分测试认证和授权流程
通过理解 JWT 和 OAuth2.0 的区别和关系,可以在实际项目中做出正确的技术选择。
服务端 · 2026年2月21日 17:53
JWT 在实际应用中会遇到哪些常见问题,如何解决JWT 在实际应用中会面临各种常见问题,以下是这些问题及其解决方案:
## 1. Token 泄露问题
### 问题
JWT 存储在客户端,可能通过 XSS 攻击被窃取。
### 解决方案
```javascript
// 使用 HttpOnly Cookie 存储 JWT
app.use((req, res, next) => {
res.cookie('token', token, {
httpOnly: true, // 防止 JavaScript 访问
secure: true, // 仅 HTTPS 传输
sameSite: 'strict', // 防止 CSRF
maxAge: 900000 // 15分钟
});
next();
});
// 或者使用双重提交 Cookie 模式
app.post('/api/data', (req, res) => {
const token = req.cookies.token;
const csrfToken = req.headers['x-csrf-token'];
// 验证 CSRF token
if (!validateCsrfToken(csrfToken)) {
return res.status(403).json({ error: 'Invalid CSRF token' });
}
// 验证 JWT
const decoded = jwt.verify(token, SECRET_KEY);
// ...
});
```
## 2. Token 无法主动撤销
### 问题
JWT 一旦签发,在过期前无法主动撤销。
### 解决方案
```javascript
// 使用 Redis 实现黑名单
const redis = require('redis');
const client = redis.createClient();
// 登出时将 token 加入黑名单
async function revokeToken(token) {
const decoded = jwt.decode(token);
const ttl = decoded.exp - Math.floor(Date.now() / 1000);
if (ttl > 0) {
await client.setex(`blacklist:${token}`, ttl, '1');
}
}
// 验证时检查黑名单
async function verifyToken(token) {
const isBlacklisted = await client.exists(`blacklist:${token}`);
if (isBlacklisted) {
throw new Error('Token has been revoked');
}
return jwt.verify(token, SECRET_KEY);
}
```
## 3. Token 过期时间过长
### 问题
Token 过期时间过长会增加安全风险。
### 解决方案
```javascript
// 使用短期 Access Token + 长期 Refresh Token
const accessToken = jwt.sign(
{ userId: user.id },
SECRET_KEY,
{ expiresIn: '15m' } // 短期
);
const refreshToken = crypto.randomBytes(40).toString('hex');
await storeRefreshToken(refreshToken, user.id, '7d'); // 长期
```
## 4. Payload 信息泄露
### 问题
JWT 的 Payload 只是 Base64 编码,任何人都可以解码查看。
### 解决方案
```javascript
// 不存储敏感信息
const token = jwt.sign({
userId: user.id, // ✅ 只存储 ID
role: user.role // ✅ 只存储角色
// ❌ 不要存储密码、手机号等敏感信息
}, SECRET_KEY);
// 如需传输敏感数据,使用 JWE 加密
const { JWE } = require('jose');
async function encryptToken(payload) {
const jwe = await new JWE.Encrypt(payload)
.setProtectedHeader({ alg: 'RSA-OAEP', enc: 'A256GCM' })
.encrypt(publicKey);
return jwe;
}
```
## 5. 跨域问题
### 问题
JWT 在跨域请求中可能遇到 CORS 问题。
### 解决方案
```javascript
// 服务器端配置 CORS
const cors = require('cors');
app.use(cors({
origin: ['https://example.com', 'https://app.example.com'],
credentials: true, // 允许携带 Cookie
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
// 前端发送请求时携带凭证
fetch('https://api.example.com/data', {
method: 'GET',
credentials: 'include', // 携带 Cookie
headers: {
'Authorization': `Bearer ${token}`
}
});
```
## 6. Token 大小问题
### 问题
JWT 包含较多信息时,token 体积过大,影响传输性能。
### 解决方案
```javascript
// 1. 使用更短的算法(ES256 vs RS256)
const token = jwt.sign(payload, privateKey, {
algorithm: 'ES256' // 比 RS256 小约 50%
});
// 2. 只存储必要信息
const token = jwt.sign({
uid: user.id, // 使用短字段名
r: user.role // 使用缩写
}, SECRET_KEY);
// 3. 使用压缩
const { deflate } = require('pako');
const compressed = deflate(JSON.stringify(payload));
```
## 7. 多设备登录问题
### 问题
用户在多个设备登录,需要管理不同设备的 token。
### 解决方案
```javascript
// 为每个设备生成独立的 refresh token
app.post('/auth/login', async (req, res) => {
const { username, password, deviceInfo } = req.body;
const user = await validateUser(username, password);
const accessToken = generateAccessToken(user.id);
const refreshToken = generateRefreshToken();
// 存储设备信息
await storeDeviceToken(user.id, {
refreshToken,
deviceInfo,
lastUsed: Date.now()
});
res.json({ accessToken, refreshToken });
});
// 获取所有登录设备
app.get('/auth/devices', authMiddleware, async (req, res) => {
const devices = await getUserDevices(req.user.userId);
res.json(devices);
});
// 登出指定设备
app.post('/auth/logout-device', authMiddleware, async (req, res) => {
const { refreshToken } = req.body;
await deleteDeviceToken(req.user.userId, refreshToken);
res.json({ success: true });
});
// 登出所有设备
app.post('/auth/logout-all', authMiddleware, async (req, res) => {
await deleteAllDeviceTokens(req.user.userId);
res.json({ success: true });
});
```
## 8. 性能问题
### 问题
每次请求都需要验证 JWT 签名,影响性能。
### 解决方案
```javascript
// 1. 使用缓存
const NodeCache = require('node-cache');
const tokenCache = new NodeCache({ stdTTL: 300 }); // 5分钟缓存
function verifyTokenCached(token) {
const cacheKey = `token:${token}`;
let decoded = tokenCache.get(cacheKey);
if (!decoded) {
decoded = jwt.verify(token, SECRET_KEY);
tokenCache.set(cacheKey, decoded);
}
return decoded;
}
// 2. 使用更快的算法(ES256)
const token = jwt.sign(payload, privateKey, {
algorithm: 'ES256' // 比 RS256 快
});
// 3. 批量验证
function verifyTokens(tokens) {
return tokens.map(token => {
try {
return { token, valid: true, decoded: jwt.verify(token, SECRET_KEY) };
} catch (error) {
return { token, valid: false, error: error.message };
}
});
}
```
## 9. 密钥管理问题
### 问题
密钥管理不当会导致严重安全问题。
### 解决方案
```javascript
// 1. 使用环境变量
const SECRET_KEY = process.env.JWT_SECRET;
// 2. 使用密钥管理服务
const AWS = require('aws-sdk');
const kms = new AWS.KMS();
async function getSecretKey() {
const result = await kms.decrypt({
CiphertextBlob: Buffer.from(process.env.ENCRYPTED_SECRET, 'base64')
}).promise();
return result.Plaintext.toString();
}
// 3. 密钥轮换
const keyVersions = {
v1: 'old-secret-key',
v2: 'current-secret-key',
v3: 'new-secret-key'
};
function verifyTokenWithKeyRotation(token) {
// 尝试使用所有密钥验证
for (const [version, key] of Object.entries(keyVersions)) {
try {
const decoded = jwt.verify(token, key);
decoded.keyVersion = version;
return decoded;
} catch (error) {
continue;
}
}
throw new Error('Invalid token');
}
```
## 10. 审计和监控
### 问题
需要监控 JWT 的使用情况,发现异常行为。
### 解决方案
```javascript
// 记录 token 使用日志
async function logTokenUsage(token, action) {
const decoded = jwt.decode(token);
await db.insert('token_logs', {
userId: decoded.userId,
token: token.substring(0, 20) + '...', // 只记录部分
action,
ip: req.ip,
userAgent: req.headers['user-agent'],
timestamp: Date.now()
});
}
// 检测异常使用
async function detectAnomalousUsage(userId) {
const logs = await getTokenLogs(userId, 24 * 60 * 60 * 1000); // 24小时
const uniqueIPs = new Set(logs.map(log => log.ip));
const uniqueLocations = new Set(logs.map(log => log.location));
if (uniqueIPs.size > 10 || uniqueLocations.size > 5) {
// 可能存在异常,发送警报
await sendAlert(userId, 'Suspicious token usage detected');
}
}
```
## 最佳实践总结
1. **使用 HttpOnly Cookie** 存储 JWT
2. **实现 Token 黑名单** 支持主动撤销
3. **使用短期 Access Token + 长期 Refresh Token**
4. **不在 Payload 中存储敏感信息**
5. **正确配置 CORS** 支持跨域
6. **优化 Token 大小** 提高性能
7. **实现多设备管理**
8. **使用缓存提升性能**
9. **安全地管理密钥**
10. **实施审计和监控**
通过解决这些常见问题,可以构建更安全、更可靠的 JWT 认证系统。
服务端 · 2026年2月21日 17:53
JWT 如何满足合规性要求JWT 的合规性对于企业级应用和受监管行业非常重要。以下是主要的合规要求和实现方法:
## 1. GDPR 合规
### 数据最小化原则
```javascript
// ❌ 不符合 GDPR: 存储过多个人信息
const token = jwt.sign({
userId: '123',
username: 'john.doe@example.com',
fullName: 'John Doe',
address: '123 Main St',
phone: '+1234567890',
ssn: '123-45-6789' // 敏感信息
}, SECRET_KEY);
// ✅ 符合 GDPR: 只存储必要信息
const token = jwt.sign({
sub: '123', // subject,用户唯一标识
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 3600
}, SECRET_KEY);
```
### 数据删除权
```javascript
// 实现用户数据删除
async function deleteUserAccount(userId) {
// 1. 刷新所有 token 到黑名单
await invalidateAllUserTokens(userId);
// 2. 删除用户数据
await db.delete('users', { id: userId });
await db.delete('user_sessions', { userId });
// 3. 记录删除操作(审计日志)
await logDataDeletion(userId, 'user_request');
return { success: true };
}
```
### 数据访问权
```javascript
// 实现数据导出功能
async function exportUserData(userId) {
const userData = {
profile: await db.get('users', { id: userId }),
sessions: await db.get('user_sessions', { userId }),
activity: await db.get('user_activity', { userId }),
exportDate: new Date().toISOString()
};
return userData;
}
```
## 2. HIPAA 合规
### 受保护健康信息(PHI)处理
```javascript
// ❌ 不符合 HIPAA: 在 JWT 中存储 PHI
const token = jwt.sign({
userId: '123',
patientName: 'John Doe',
diagnosis: 'Hypertension',
medications: ['Lisinopril', 'Amlodipine']
}, SECRET_KEY);
// ✅ 符合 HIPAA: 只存储引用 ID
const token = jwt.sign({
sub: '123',
scope: 'patient.read',
aud: 'healthcare-api'
}, SECRET_KEY);
// PHI 存储在安全的数据库中
const patientData = await db.get('patients', { id: '123' });
```
### 审计日志
```javascript
// 记录所有 PHI 访问
async function logPHIAccess(userId, patientId, action) {
await db.insert('audit_log', {
userId,
patientId,
action,
timestamp: new Date().toISOString(),
ip: req.ip,
userAgent: req.headers['user-agent'],
accessedFields: ['diagnosis', 'medications']
});
}
// 使用示例
app.get('/api/patients/:id', authMiddleware, async (req, res) => {
const patient = await db.get('patients', { id: req.params.id });
// 记录访问
await logPHIAccess(req.user.userId, req.params.id, 'READ');
res.json(patient);
});
```
### 最小权限原则
```javascript
// 实现细粒度权限控制
function requirePHIAccess(requiredScope) {
return (req, res, next) => {
const userScope = req.user.scope || [];
if (!userScope.includes(requiredScope)) {
return res.status(403).json({
error: 'INSUFFICIENT_PERMISSIONS',
message: 'You do not have permission to access this resource'
});
}
next();
};
}
// 使用示例
app.get('/api/patients/:id',
authMiddleware,
requirePHIAccess('patient.read'),
async (req, res) => {
const patient = await db.get('patients', { id: req.params.id });
res.json(patient);
}
);
```
## 3. PCI DSS 合规
### 不存储敏感信息
```javascript
// ❌ 不符合 PCI DSS: 在 JWT 中存储卡号
const token = jwt.sign({
userId: '123',
cardNumber: '4111111111111111',
expiry: '12/25',
cvv: '123'
}, SECRET_KEY);
// ✅ 符合 PCI DSS: 只存储令牌化后的引用
const token = jwt.sign({
sub: '123',
paymentToken: 'tok_1abc2def3ghi',
scope: 'payment.process'
}, SECRET_KEY);
```
### 加密传输
```javascript
// 强制使用 HTTPS
const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('private-key.pem'),
cert: fs.readFileSync('certificate.pem'),
ca: fs.readFileSync('ca-bundle.crt')
};
const server = https.createServer(options, app);
// HTTP 到 HTTPS 重定向
app.use((req, res, next) => {
if (!req.secure) {
return res.redirect(`https://${req.headers.host}${req.url}`);
}
next();
});
```
### 安全的密钥管理
```javascript
// 使用 AWS KMS 管理密钥
const AWS = require('aws-sdk');
const kms = new AWS.KMS();
async function encryptData(data) {
const params = {
KeyId: process.env.KMS_KEY_ID,
Plaintext: Buffer.from(data)
};
const result = await kms.encrypt(params).promise();
return result.CiphertextBlob.toString('base64');
}
async function decryptData(encryptedData) {
const params = {
CiphertextBlob: Buffer.from(encryptedData, 'base64')
};
const result = await kms.decrypt(params).promise();
return result.Plaintext.toString();
}
```
## 4. SOC 2 合规
### 访问控制
```javascript
// 实现基于角色的访问控制(RBAC)
const roles = {
admin: ['read', 'write', 'delete', 'manage'],
user: ['read', 'write'],
guest: ['read']
};
function checkPermission(user, requiredPermission) {
const userRole = user.role || 'guest';
const userPermissions = roles[userRole] || [];
return userPermissions.includes(requiredPermission);
}
// 中间件
function requirePermission(permission) {
return (req, res, next) => {
if (!checkPermission(req.user, permission)) {
return res.status(403).json({
error: 'FORBIDDEN',
message: 'Insufficient permissions'
});
}
next();
};
}
```
### 审计追踪
```javascript
// 全面的审计日志
async function auditLog(event, data) {
const logEntry = {
event,
data,
timestamp: new Date().toISOString(),
userId: data.userId,
ipAddress: data.ip,
userAgent: data.userAgent,
sessionId: data.sessionId
};
// 存储到不可变的日志存储
await appendToAuditLog(logEntry);
// 同时发送到 SIEM 系统
await sendToSIEM(logEntry);
}
// 使用示例
app.post('/api/users', authMiddleware, async (req, res) => {
const user = await createUser(req.body);
await auditLog('USER_CREATED', {
userId: req.user.userId,
targetUserId: user.id,
ip: req.ip,
userAgent: req.headers['user-agent']
});
res.json(user);
});
```
### 变更管理
```javascript
// 记录配置变更
async function logConfigChange(configKey, oldValue, newValue, userId) {
await db.insert('config_changes', {
configKey,
oldValue,
newValue,
userId,
timestamp: new Date().toISOString(),
changeType: 'MODIFICATION'
});
}
// 使用示例
async function updateJWTConfig(newConfig) {
const oldConfig = await getCurrentJWTConfig();
await logConfigChange(
'jwt_config',
oldConfig,
newConfig,
req.user.userId
);
await saveJWTConfig(newConfig);
}
```
## 5. ISO 27001 合规
### 信息安全策略
```javascript
// 实现安全策略
const securityPolicies = {
passwordPolicy: {
minLength: 12,
requireUppercase: true,
requireLowercase: true,
requireNumbers: true,
requireSpecialChars: true,
maxAge: 90 // days
},
tokenPolicy: {
accessTokenExpiry: 900, // 15 minutes
refreshTokenExpiry: 604800, // 7 days
maxConcurrentSessions: 5
},
sessionPolicy: {
idleTimeout: 1800, // 30 minutes
absoluteTimeout: 28800 // 8 hours
}
};
// 验证密码策略
function validatePassword(password) {
const policy = securityPolicies.passwordPolicy;
if (password.length < policy.minLength) {
throw new Error('Password too short');
}
if (policy.requireUppercase && !/[A-Z]/.test(password)) {
throw new Error('Password must contain uppercase letters');
}
// ... 其他验证
return true;
}
```
### 风险评估
```javascript
// 实现风险评估
async function assessSecurityRisk(userId, action) {
const riskFactors = {
unusualLocation: await checkUnusualLocation(userId),
unusualTime: await checkUnusualTime(userId),
multipleFailedAttempts: await checkFailedAttempts(userId),
newDevice: await checkNewDevice(userId)
};
const riskScore = Object.values(riskFactors)
.filter(Boolean)
.length;
if (riskScore >= 2) {
// 高风险,需要额外验证
return { risk: 'HIGH', requireMFA: true };
}
return { risk: 'LOW', requireMFA: false };
}
// 使用示例
app.post('/auth/login', async (req, res) => {
const { username, password } = req.body;
const user = await validateUser(username, password);
const riskAssessment = await assessSecurityRisk(user.id, 'LOGIN');
if (riskAssessment.requireMFA) {
return res.json({
requireMFA: true,
mfaMethods: ['sms', 'totp']
});
}
const token = generateToken(user);
res.json({ token });
});
```
## 6. 合规检查清单
### GDPR 检查清单
- [ ] 实施数据最小化原则
- [ ] 提供数据访问权
- [ ] 实现数据删除权
- [ ] 获得明确的同意
- [ ] 实施数据保护措施
- [ ] 记录数据处理活动
- [ ] 指定数据保护官(DPO)
### HIPAA 检查清单
- [ ] 保护 PHI 的机密性
- [ ] 实施访问控制
- [ ] 记录所有 PHI 访问
- [ ] 实施最小权限原则
- [ ] 定期进行风险评估
- [ ] 签署商业伙伴协议(BAA)
- [ ] 培训员工安全意识
### PCI DSS 检查清单
- [ ] 不存储完整的卡号
- [ ] 使用令牌化技术
- [ ] 加密传输数据
- [ ] 使用强加密算法
- [ ] 定期更新密钥
- [ ] 实施访问控制
- [ ] 进行定期安全测试
### SOC 2 检查清单
- [ ] 实施访问控制
- [ ] 记录所有系统活动
- [ ] 实施变更管理流程
- [ ] 定期进行审计
- [ ] 建立事件响应计划
- [ ] 进行员工背景调查
- [ ] 定期进行安全培训
### ISO 27001 检查清单
- [ ] 建立信息安全策略
- [ ] 实施风险评估
- [ ] 建立访问控制
- [ ] 实施密码策略
- [ ] 定期进行安全审计
- [ ] 建立事件响应流程
- [ ] 持续改进安全措施
## 7. 最佳实践
### 合规性最佳实践
1. **了解适用的法规**: 确定哪些合规要求适用于你的业务
2. **实施最小权限**: 只授予必要的访问权限
3. **记录所有活动**: 维护完整的审计日志
4. **定期审计**: 定期检查合规性
5. **培训员工**: 确保员工了解合规要求
6. **使用加密**: 保护敏感数据
7. **实施访问控制**: 限制对敏感信息的访问
8. **建立事件响应**: 准备应对安全事件
9. **定期更新**: 保持系统和策略的最新
10. **寻求专业建议**: 必要时咨询合规专家
通过遵循这些合规要求,可以确保 JWT 认证系统符合各种法规和标准。
服务端 · 2026年2月21日 17:52
JWT 的安全性有哪些风险,如何防护JWT 的安全性是面试中常问的话题,以下是 JWT 的主要安全风险和防护措施:
## 主要安全风险
### 1. 算法混淆攻击 (Algorithm Confusion Attack)
攻击者将签名算法从非对称(如 RS256)改为对称(如 HS256),并使用公钥作为密钥来伪造签名。
**防护措施**:
- 严格验证签名算法,不允许客户端指定算法
- 在服务器端固定使用安全的签名算法
- 拒绝使用 `none` 算法
### 2. Token 泄露
JWT 存储在客户端,可能通过 XSS 攻击被窃取。
**防护措施**:
- 使用 HttpOnly Cookie 存储 JWT,防止 JavaScript 访问
- 设置 SameSite 属性为 Strict 或 Lax,防止 CSRF 攻击
- 使用 HTTPS 传输,防止中间人攻击
- 设置较短的过期时间,并实现 Token 刷新机制
### 3. Payload 信息泄露
JWT 的 Payload 只是 Base64 编码,不是加密,任何人都可以解码查看。
**防护措施**:
- 不要在 Payload 中存储敏感信息(如密码、身份证号)
- 只存储必要的用户标识信息
- 如需传输敏感数据,使用 JWE (JSON Web Encryption)
### 4. Token 无法主动撤销
JWT 一旦签发,在过期前无法主动撤销。
**防护措施**:
- 设置合理的过期时间(通常 15-30 分钟)
- 实现 Token 黑名单机制(使用 Redis 存储)
- 使用 Refresh Token 机制,短期 Access Token + 长期 Refresh Token
- 在关键操作时进行二次验证
### 5. 重放攻击
攻击者截获有效的 JWT 并重复使用。
**防护措施**:
- 使用 jti (JWT ID) 声明,每个 Token 唯一
- 实现已使用 Token 的黑名单
- 添加 nonce 或时间戳验证
## 安全最佳实践
### 1. 签名算法选择
- **HS256**: HMAC SHA256,对称加密,密钥管理复杂
- **RS256**: RSA SHA256,非对称加密,更安全,推荐使用
- **ES256**: ECDSA SHA256,性能更好,签名更短
- **避免使用**: `none` 算法、弱算法(HS1、HS512)
### 2. 密钥管理
- 使用足够强度的密钥(至少 256 位)
- 定期轮换密钥
- 密钥存储在安全的地方(环境变量、密钥管理服务)
- 不要在代码中硬编码密钥
### 3. Token 配置
```javascript
{
"iss": "your-domain.com", // 签发者
"sub": "user-id", // 主题
"aud": "your-api", // 受众
"exp": 1516239022, // 过期时间
"nbf": 1516239022, // 生效时间
"iat": 1516239022, // 签发时间
"jti": "unique-token-id" // JWT ID
}
```
### 4. 传输安全
- 始终使用 HTTPS
- 在 Authorization Header 中传递: `Bearer <token>`
- 不要在 URL 中传递 JWT
### 5. 存储安全
- 优先使用 HttpOnly Cookie
- 如使用 LocalStorage,确保防范 XSS
- 实现 Token 自动刷新机制
### 6. 监控和审计
- 记录 Token 的签发、验证、刷新操作
- 监控异常的 Token 使用模式
- 实现速率限制,防止暴力破解
## 安全检查清单
- [ ] 使用强签名算法(RS256 或 ES256)
- [ ] 设置合理的过期时间
- [ ] 使用 HTTPS 传输
- [ ] 不存储敏感信息在 Payload 中
- [ ] 实现 Token 刷新机制
- [ ] 添加 jti 声明防止重放
- [ ] 使用 HttpOnly Cookie 存储
- [ ] 实现 Token 黑名单
- [ ] 密钥安全存储和定期轮换
- [ ] 监控和日志记录
通过以上措施,可以大大提高 JWT 的安全性。
服务端 · 2026年2月21日 17:52
如何优化 JWT 的性能JWT 的性能优化对于高并发系统非常重要。以下是主要的优化策略和实现方法:
## 1. 签名算法优化
### 选择更快的算法
```javascript
// ES256 比 RS256 更快,签名更小
const jwt = require('jsonwebtoken');
// 使用 ES256 (ECDSA)
const token = jwt.sign(payload, privateKey, {
algorithm: 'ES256' // 比 RS256 快约 2-3 倍
});
// HS256 最快,但需要安全地管理密钥
const token = jwt.sign(payload, secretKey, {
algorithm: 'HS256' // 最快,但密钥管理复杂
});
```
### 算法性能对比
| 算法 | 签名速度 | 验证速度 | 签名大小 | 推荐场景 |
|------|---------|---------|----------|---------|
| HS256 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ~32B | 单体应用 |
| RS256 | ⭐⭐ | ⭐⭐⭐ | ~256B | 分布式系统 |
| ES256 | ⭐⭐⭐ | ⭐⭐⭐⭐ | ~64B | 移动应用 |
## 2. 缓存优化
### 验证结果缓存
```javascript
const NodeCache = require('node-cache');
const tokenCache = new NodeCache({
stdTTL: 300, // 5分钟缓存
checkperiod: 60
});
function verifyTokenCached(token) {
const cacheKey = `token:${token}`;
// 检查缓存
const cached = tokenCache.get(cacheKey);
if (cached) {
return cached;
}
// 验证 token
const decoded = jwt.verify(token, SECRET_KEY);
// 缓存结果
tokenCache.set(cacheKey, decoded);
return decoded;
}
// 使用示例
app.get('/api/data', (req, res) => {
const token = req.headers['authorization']?.replace('Bearer ', '');
const decoded = verifyTokenCached(token); // 使用缓存
res.json({ data: '...' });
});
```
### 公钥缓存
```javascript
const jose = require('node-jose');
let cachedKeyStore = null;
let cacheExpiry = 0;
async function getKeyStore() {
if (cachedKeyStore && Date.now() < cacheExpiry) {
return cachedKeyStore;
}
// 从远程获取 JWK Set
const response = await fetch('https://auth.example.com/.well-known/jwks.json');
const jwks = await response.json();
// 创建 keystore
const keystore = await jose.JWK.createKeyStore();
for (const jwk of jwks.keys) {
await keystore.add(jwk);
}
cachedKeyStore = keystore;
cacheExpiry = Date.now() + (5 * 60 * 1000); // 5分钟
return keystore;
}
```
## 3. Token 大小优化
### 减少 Payload 大小
```javascript
// ❌ 不好的做法:字段名过长
const token = jwt.sign({
userId: '1234567890',
userName: 'john.doe@example.com',
userRole: 'administrator',
userPermissions: ['read', 'write', 'delete']
}, SECRET_KEY);
// ✅ 好的做法:使用短字段名
const token = jwt.sign({
uid: '1234567890', // userId -> uid
unm: 'john.doe@example.com', // userName -> unm
rol: 'admin', // userRole -> rol
prms: ['r', 'w', 'd'] // userPermissions -> prms
}, SECRET_KEY);
// ✅ 更好的做法:只存储 ID,其他信息从数据库获取
const token = jwt.sign({
uid: '1234567890'
}, SECRET_KEY);
```
### 使用压缩
```javascript
const { deflate, inflate } = require('pako');
function compressPayload(payload) {
const json = JSON.stringify(payload);
const compressed = deflate(json);
return compressed.toString('base64');
}
function decompressPayload(compressed) {
const buffer = Buffer.from(compressed, 'base64');
const decompressed = inflate(buffer);
return JSON.parse(decompressed.toString());
}
// 使用示例
const payload = { uid: '123', unm: 'john', rol: 'admin' };
const compressed = compressPayload(payload);
const token = jwt.sign({ data: compressed }, SECRET_KEY);
```
## 4. 异步验证
### 使用异步 API
```javascript
// ❌ 同步验证(阻塞)
app.get('/api/data', (req, res) => {
const token = req.headers['authorization']?.replace('Bearer ', '');
const decoded = jwt.verify(token, SECRET_KEY); // 阻塞
res.json({ data: '...' });
});
// ✅ 异步验证(非阻塞)
app.get('/api/data', async (req, res) => {
const token = req.headers['authorization']?.replace('Bearer ', '');
try {
const decoded = await verifyTokenAsync(token);
res.json({ data: '...' });
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
});
// 异步验证函数
function verifyTokenAsync(token) {
return new Promise((resolve, reject) => {
jwt.verify(token, SECRET_KEY, (err, decoded) => {
if (err) {
reject(err);
} else {
resolve(decoded);
}
});
});
}
```
## 5. 批量验证
### 批量验证多个 Token
```javascript
function verifyTokensBatch(tokens) {
return tokens.map(token => {
try {
const decoded = jwt.verify(token, SECRET_KEY);
return { token, valid: true, decoded };
} catch (error) {
return { token, valid: false, error: error.message };
}
});
}
// 使用示例
const tokens = ['token1', 'token2', 'token3'];
const results = verifyTokensBatch(tokens);
const validTokens = results.filter(r => r.valid);
```
## 6. 数据库优化
### 减少数据库查询
```javascript
// ❌ 每次请求都查询数据库
app.get('/api/user', async (req, res) => {
const decoded = jwt.verify(token, SECRET_KEY);
const user = await db.query('SELECT * FROM users WHERE id = ?', [decoded.userId]);
res.json(user);
});
// ✅ 使用 Redis 缓存用户信息
const redis = require('redis');
const client = redis.createClient();
app.get('/api/user', async (req, res) => {
const decoded = jwt.verify(token, SECRET_KEY);
// 先从缓存获取
let user = await client.get(`user:${decoded.userId}`);
if (!user) {
// 缓存未命中,查询数据库
user = await db.query('SELECT * FROM users WHERE id = ?', [decoded.userId]);
// 存入缓存
await client.setex(`user:${decoded.userId}`, 300, JSON.stringify(user));
} else {
user = JSON.parse(user);
}
res.json(user);
});
```
## 7. 连接池优化
### 使用连接池
```javascript
const mysql = require('mysql2/promise');
// 创建连接池
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'password',
database: 'mydb',
waitForConnections: true,
connectionLimit: 10, // 连接池大小
queueLimit: 0
});
app.get('/api/data', async (req, res) => {
const decoded = jwt.verify(token, SECRET_KEY);
// 使用连接池
const [rows] = await pool.query('SELECT * FROM data WHERE user_id = ?', [decoded.userId]);
res.json(rows);
});
```
## 8. 负载均衡
### 多实例部署
```javascript
// 使用 Redis 共享缓存
const redis = require('redis');
const client = redis.createClient({
host: 'redis-server',
port: 6379
});
// 所有实例共享同一个 Redis 缓存
function verifyTokenCached(token) {
return new Promise(async (resolve, reject) => {
const cacheKey = `token:${token}`;
// 从 Redis 获取缓存
const cached = await client.get(cacheKey);
if (cached) {
return resolve(JSON.parse(cached));
}
// 验证 token
const decoded = jwt.verify(token, SECRET_KEY);
// 存入 Redis
await client.setex(cacheKey, 300, JSON.stringify(decoded));
resolve(decoded);
});
}
```
## 9. 监控和调优
### 性能监控
```javascript
const promClient = require('prom-client');
// 创建指标
const tokenVerifyDuration = new promClient.Histogram({
name: 'token_verify_duration_seconds',
help: 'Token verification duration in seconds',
buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1]
});
const tokenCacheHits = new promClient.Counter({
name: 'token_cache_hits_total',
help: 'Total number of token cache hits'
});
const tokenCacheMisses = new prom.Client.Counter({
name: 'token_cache_misses_total',
help: 'Total number of token cache misses'
});
// 监控验证性能
function verifyTokenWithMetrics(token) {
const end = tokenVerifyDuration.startTimer();
try {
const decoded = verifyTokenCached(token);
tokenCacheHits.inc();
return decoded;
} catch (error) {
tokenCacheMisses.inc();
throw error;
} finally {
end();
}
}
// 指标端点
app.get('/metrics', (req, res) => {
res.set('Content-Type', promClient.register.contentType);
res.end(promClient.register.metrics());
});
```
## 10. 最佳实践总结
### 性能优化清单
- [ ] 使用更快的签名算法(ES256)
- [ ] 实现验证结果缓存
- [ ] 减少 Payload 大小
- [ ] 使用异步验证
- [ ] 批量验证多个 Token
- [ ] 减少数据库查询
- [ ] 使用连接池
- [ ] 实现负载均衡
- [ ] 添加性能监控
- [ ] 定期分析和优化
### 性能基准测试
```javascript
const Benchmark = require('benchmark');
const suite = new Benchmark.Suite;
suite
.add('HS256', () => {
jwt.sign(payload, secretKey, { algorithm: 'HS256' });
})
.add('RS256', () => {
jwt.sign(payload, privateKey, { algorithm: 'RS256' });
})
.add('ES256', () => {
jwt.sign(payload, privateKey, { algorithm: 'ES256' });
})
.on('cycle', (event) => {
console.log(String(event.target));
})
.run();
```
通过以上优化策略,可以显著提升 JWT 认证系统的性能,支持更高的并发量。
服务端 · 2026年2月21日 17:52
如何对 JWT 进行测试JWT 的测试是确保认证系统安全可靠的重要环节。以下是完整的测试策略和实现方法:
## 1. 单元测试
### 测试 Token 生成
```javascript
const jwt = require('jsonwebtoken');
const { expect } = require('chai');
describe('JWT Token Generation', () => {
const SECRET_KEY = 'test-secret-key';
const payload = { userId: '123', username: 'testuser' };
it('should generate a valid token', () => {
const token = jwt.sign(payload, SECRET_KEY);
expect(token).to.be.a('string');
expect(token.split('.')).to.have.lengthOf(3);
});
it('should generate token with correct payload', () => {
const token = jwt.sign(payload, SECRET_KEY);
const decoded = jwt.decode(token);
expect(decoded.userId).to.equal('123');
expect(decoded.username).to.equal('testuser');
});
it('should generate token with expiration time', () => {
const token = jwt.sign(payload, SECRET_KEY, { expiresIn: '1h' });
const decoded = jwt.decode(token);
expect(decoded.exp).to.be.a('number');
expect(decoded.exp).to.be.greaterThan(Math.floor(Date.now() / 1000));
});
it('should generate token with custom claims', () => {
const customPayload = {
...payload,
iss: 'test-issuer',
aud: 'test-audience'
};
const token = jwt.sign(customPayload, SECRET_KEY);
const decoded = jwt.decode(token);
expect(decoded.iss).to.equal('test-issuer');
expect(decoded.aud).to.equal('test-audience');
});
});
```
### 测试 Token 验证
```javascript
describe('JWT Token Verification', () => {
const SECRET_KEY = 'test-secret-key';
const payload = { userId: '123', username: 'testuser' };
it('should verify a valid token', () => {
const token = jwt.sign(payload, SECRET_KEY);
const decoded = jwt.verify(token, SECRET_KEY);
expect(decoded.userId).to.equal('123');
expect(decoded.username).to.equal('testuser');
});
it('should throw error for invalid token', () => {
const invalidToken = 'invalid.token.here';
expect(() => {
jwt.verify(invalidToken, SECRET_KEY);
}).to.throw('jwt malformed');
});
it('should throw error for expired token', () => {
const expiredToken = jwt.sign(payload, SECRET_KEY, { expiresIn: '-1s' });
expect(() => {
jwt.verify(expiredToken, SECRET_KEY);
}).to.throw('jwt expired');
});
it('should throw error for wrong secret', () => {
const token = jwt.sign(payload, SECRET_KEY);
const wrongSecret = 'wrong-secret';
expect(() => {
jwt.verify(token, wrongSecret);
}).to.throw('invalid signature');
});
it('should verify token with algorithm check', () => {
const token = jwt.sign(payload, SECRET_KEY, { algorithm: 'HS256' });
const decoded = jwt.verify(token, SECRET_KEY, { algorithms: ['HS256'] });
expect(decoded.userId).to.equal('123');
});
it('should reject token with wrong algorithm', () => {
const token = jwt.sign(payload, SECRET_KEY, { algorithm: 'HS256' });
expect(() => {
jwt.verify(token, SECRET_KEY, { algorithms: ['RS256'] });
}).to.throw('invalid algorithm');
});
});
```
## 2. 集成测试
### 测试认证流程
```javascript
const request = require('supertest');
const app = require('../app');
describe('Authentication Flow Integration Tests', () => {
let authToken;
it('should login and receive token', async () => {
const response = await request(app)
.post('/auth/login')
.send({
username: 'testuser',
password: 'password123'
})
.expect(200);
expect(response.body).to.have.property('token');
expect(response.body).to.have.property('expiresIn');
authToken = response.body.token;
});
it('should access protected route with valid token', async () => {
const response = await request(app)
.get('/api/protected')
.set('Authorization', `Bearer ${authToken}`)
.expect(200);
expect(response.body).to.have.property('data');
});
it('should reject request without token', async () => {
await request(app)
.get('/api/protected')
.expect(401);
});
it('should reject request with invalid token', async () => {
await request(app)
.get('/api/protected')
.set('Authorization', 'Bearer invalid-token')
.expect(401);
});
it('should reject request with expired token', async () => {
const expiredToken = jwt.sign(
{ userId: '123' },
process.env.JWT_SECRET,
{ expiresIn: '-1s' }
);
await request(app)
.get('/api/protected')
.set('Authorization', `Bearer ${expiredToken}`)
.expect(401);
});
});
```
### 测试 Token 刷新
```javascript
describe('Token Refresh Integration Tests', () => {
let accessToken;
let refreshToken;
it('should login and receive both tokens', async () => {
const response = await request(app)
.post('/auth/login')
.send({
username: 'testuser',
password: 'password123'
})
.expect(200);
accessToken = response.body.accessToken;
refreshToken = response.body.refreshToken;
expect(accessToken).to.exist;
expect(refreshToken).to.exist;
});
it('should refresh access token', async () => {
const response = await request(app)
.post('/auth/refresh')
.send({ refreshToken })
.expect(200);
expect(response.body).to.have.property('accessToken');
expect(response.body.accessToken).to.not.equal(accessToken);
accessToken = response.body.accessToken;
});
it('should access protected route with new token', async () => {
await request(app)
.get('/api/protected')
.set('Authorization', `Bearer ${accessToken}`)
.expect(200);
});
it('should reject refresh with invalid token', async () => {
await request(app)
.post('/auth/refresh')
.send({ refreshToken: 'invalid-refresh-token' })
.expect(401);
});
});
```
## 3. 安全测试
### 测试算法混淆攻击
```javascript
describe('Security Tests - Algorithm Confusion', () => {
it('should reject tokens with none algorithm', () => {
const maliciousToken = jwt.sign(
{ userId: 'admin' },
'',
{ algorithm: 'none' }
);
expect(() => {
jwt.verify(maliciousToken, SECRET_KEY, { algorithms: ['HS256'] });
}).to.throw();
});
it('should reject tokens with unspecified algorithm', () => {
const token = jwt.sign({ userId: '123' }, SECRET_KEY);
// 尝试使用不同的算法验证
expect(() => {
jwt.verify(token, SECRET_KEY, { algorithms: ['none'] });
}).to.throw('invalid algorithm');
});
});
```
### 测试 Token 篡改
```javascript
describe('Security Tests - Token Tampering', () => {
it('should reject tampered token', () => {
const token = jwt.sign({ userId: '123' }, SECRET_KEY);
// 篡改 token
const parts = token.split('.');
parts[1] = Buffer.from(JSON.stringify({ userId: 'admin' })).toString('base64');
const tamperedToken = parts.join('.');
expect(() => {
jwt.verify(tamperedToken, SECRET_KEY);
}).to.throw('invalid signature');
});
});
```
## 4. 性能测试
### 测试 Token 生成性能
```javascript
const Benchmark = require('benchmark');
const suite = new Benchmark.Suite();
describe('Performance Tests', () => {
it('should benchmark token generation', function(done) {
this.timeout(10000);
suite
.add('HS256', () => {
jwt.sign({ userId: '123' }, SECRET_KEY, { algorithm: 'HS256' });
})
.add('RS256', () => {
jwt.sign({ userId: '123' }, privateKey, { algorithm: 'RS256' });
})
.add('ES256', () => {
jwt.sign({ userId: '123' }, privateKey, { algorithm: 'ES256' });
})
.on('complete', function() {
console.log('Fastest is ' + this.filter('fastest').map('name'));
done();
})
.run({ async: true });
});
it('should handle concurrent token generation', async () => {
const concurrency = 1000;
const promises = [];
for (let i = 0; i < concurrency; i++) {
promises.push(
new Promise((resolve) => {
const start = Date.now();
jwt.sign({ userId: i.toString() }, SECRET_KEY);
resolve(Date.now() - start);
})
);
}
const times = await Promise.all(promises);
const avgTime = times.reduce((a, b) => a + b, 0) / times.length;
expect(avgTime).to.be.below(10); // 平均时间应小于 10ms
});
});
```
## 5. Mock 和 Stub
### 使用 Mock 测试
```javascript
const sinon = require('sinon');
describe('Authentication with Mocks', () => {
let verifyStub;
beforeEach(() => {
verifyStub = sinon.stub(jwt, 'verify');
});
afterEach(() => {
verifyStub.restore();
});
it('should handle valid token with mock', async () => {
verifyStub.returns({ userId: '123', username: 'testuser' });
const response = await request(app)
.get('/api/protected')
.set('Authorization', 'Bearer mock-token')
.expect(200);
expect(verifyStub.calledOnce).to.be.true;
});
it('should handle invalid token with mock', async () => {
verifyStub.throws(new Error('Invalid token'));
const response = await request(app)
.get('/api/protected')
.set('Authorization', 'Bearer invalid-token')
.expect(401);
expect(verifyStub.calledOnce).to.be.true;
});
});
```
## 6. 测试覆盖率
### 使用 Istanbul/nyc
```javascript
// package.json
{
"scripts": {
"test": "jest",
"test:coverage": "jest --coverage",
"test:watch": "jest --watch"
},
"jest": {
"collectCoverageFrom": [
"src/**/*.js",
"!src/**/*.test.js"
],
"coverageThreshold": {
"global": {
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
}
}
}
}
```
## 7. 端到端测试
### 使用 Cypress
```javascript
// cypress/integration/auth.spec.js
describe('Authentication E2E Tests', () => {
it('should login successfully', () => {
cy.visit('/login');
cy.get('input[name="username"]').type('testuser');
cy.get('input[name="password"]').type('password123');
cy.get('button[type="submit"]').click();
cy.url().should('include', '/dashboard');
cy.get('[data-testid="user-info"]').should('contain', 'testuser');
});
it('should redirect to login on token expiry', () => {
cy.window().then((win) => {
// 模拟 token 过期
win.localStorage.setItem('token', 'expired-token');
});
cy.visit('/dashboard');
cy.url().should('include', '/login');
});
it('should refresh token automatically', () => {
cy.login('testuser', 'password123');
// 等待 token 刷新
cy.wait(15000);
// 验证可以继续访问受保护的路由
cy.visit('/dashboard');
cy.url().should('include', '/dashboard');
});
});
```
## 8. 测试最佳实践
### 测试清单
- [ ] 测试 Token 生成
- [ ] 测试 Token 验证
- [ ] 测试过期处理
- [ ] 测试无效 Token
- [ ] 测试安全漏洞
- [ ] 测试性能
- [ ] 测试并发
- [ ] 测试边界情况
- [ ] 测试错误处理
- [ ] 达到测试覆盖率目标
### 测试工具推荐
- **单元测试**: Jest, Mocha, Chai
- **集成测试**: Supertest
- **端到端测试**: Cypress, Playwright
- **性能测试**: Benchmark.js, Artillery
- **安全测试**: OWASP ZAP, Burp Suite
- **覆盖率**: Istanbul/nyc, Jest Coverage
通过完整的测试策略,可以确保 JWT 认证系统的安全性、可靠性和性能。
服务端 · 2026年2月21日 17:52
JWT 的签名算法有哪些,如何选择JWT 的签名算法是保证其安全性的关键,以下是主要签名算法的对比和选择建议:
## 主要签名算法
### 1. HS256 (HMAC SHA256)
**对称加密算法**,使用相同的密钥进行签名和验证。
**特点**:
- 性能较好,计算速度快
- 密钥管理复杂,需要安全地共享密钥
- 适合单服务应用
**示例**:
```javascript
const token = jwt.sign(payload, 'secret-key', { algorithm: 'HS256' });
const decoded = jwt.verify(token, 'secret-key', { algorithms: ['HS256'] });
```
**适用场景**:
- 单体应用
- 服务端对服务端的认证
- 内部系统通信
### 2. RS256 (RSA SHA256)
**非对称加密算法**,使用私钥签名,公钥验证。
**特点**:
- 安全性更高,私钥不泄露
- 公钥可以公开分发
- 密钥管理相对简单
- 性能较慢
**示例**:
```javascript
const privateKey = fs.readFileSync('private.key');
const publicKey = fs.readFileSync('public.key');
// 签名(使用私钥)
const token = jwt.sign(payload, privateKey, { algorithm: 'RS256' });
// 验证(使用公钥)
const decoded = jwt.verify(token, publicKey, { algorithms: ['RS256'] });
```
**适用场景**:
- 分布式系统
- 公共 API
- 第三方集成
- 需要高安全性的场景
### 3. ES256 (ECDSA SHA256)
**椭圆曲线数字签名算法**,使用私钥签名,公钥验证。
**特点**:
- 签名更小(比 RSA 小约 50%)
- 性能比 RSA 更好
- 安全性高
- 密钥生成速度快
**示例**:
```javascript
const privateKey = fs.readFileSync('ec-private.key');
const publicKey = fs.readFileSync('ec-public.key');
const token = jwt.sign(payload, privateKey, { algorithm: 'ES256' });
const decoded = jwt.verify(token, publicKey, { algorithms: ['ES256'] });
```
**适用场景**:
- 移动应用(减少传输数据量)
- IoT 设备
- 需要高性能的场景
### 4. PS256 (RSA-PSS SHA256)
**RSA-PSS 签名算法**,比 RS256 更安全。
**特点**:
- 提供更强的安全性证明
- 签名大小与 RS256 相同
- 性能与 RS256 相似
**适用场景**:
- 对安全性要求极高的系统
- 金融、医疗等敏感领域
## 算法对比
| 算法 | 类型 | 密钥对 | 签名大小 | 性能 | 安全性 | 推荐度 |
|------|------|--------|----------|------|--------|--------|
| HS256 | 对称 | 单密钥 | ~32B | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
| RS256 | 非对称 | 公私钥 | ~256B | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| ES256 | 非对称 | 公私钥 | ~64B | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| PS256 | 非对称 | 公私钥 | ~256B | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
## 密钥生成
### 生成 RSA 密钥对
```bash
# 生成私钥
openssl genrsa -out private.key 2048
# 生成公钥
openssl rsa -in private.key -pubout -out public.key
```
### 生成 ECDSA 密钥对
```bash
# 生成私钥
openssl ecparam -name prime256v1 -genkey -noout -out ec-private.key
# 生成公钥
openssl ec -in ec-private.key -pubout -out ec-public.key
```
## 选择建议
### 选择 HS256 的情况
- 应用规模较小,单服务部署
- 密钥可以安全地存储和共享
- 对性能要求极高
- 内部系统通信
### 选择 RS256 的情况(推荐)
- 分布式系统,多服务部署
- 需要高安全性
- 公开 API 或第三方集成
- 标准的 JWT 实现
### 选择 ES256 的情况
- 移动应用或 IoT 设备
- 需要减少传输数据量
- 对性能有较高要求
- 签名大小敏感的场景
### 选择 PS256 的情况
- 金融、医疗等高安全要求领域
- 需要最强的安全性证明
- 不介意性能开销
## 安全注意事项
1. **永远不要使用 `none` 算法**
```javascript
// 危险!不要这样做
const token = jwt.sign(payload, '', { algorithm: 'none' });
```
2. **验证时指定算法**
```javascript
// 安全做法
const decoded = jwt.verify(token, key, {
algorithms: ['RS256'] // 明确指定允许的算法
});
```
3. **使用足够强度的密钥**
- RSA: 至少 2048 位,推荐 3072 或 4096 位
- ECDSA: 至少 P-256,推荐 P-384 或 P-521
- HMAC: 至少 256 位
4. **定期轮换密钥**
- 建议每 6-12 个月轮换一次
- 实现密钥版本管理
- 支持多密钥验证(平滑过渡)
5. **安全存储密钥**
- 使用环境变量或密钥管理服务
- 不要将密钥提交到代码仓库
- 限制密钥的访问权限
## 性能优化建议
1. **缓存公钥**: 验证时缓存公钥,避免重复读取
2. **使用 ES256**: 在安全性和性能之间取得平衡
3. **批量验证**: 对于大量 token,考虑批量验证
4. **异步验证**: 使用异步 API 避免阻塞
通过合理选择签名算法,可以在安全性、性能和易用性之间找到最佳平衡。
服务端 · 2026年2月21日 17:52
JWT 的过期时间如何设置,如何实现刷新机制JWT 的过期时间管理和刷新机制是保证安全性和用户体验的重要部分。以下是详细的实现方案:
## 过期时间设置
### 1. 基本过期时间设置
```javascript
const jwt = require('jsonwebtoken');
// 使用 expiresIn 参数
const token = jwt.sign(payload, 'secret', {
expiresIn: '1h' // 1小时
// expiresIn: '30m' // 30分钟
// expiresIn: '2d' // 2天
// expiresIn: '7d' // 7天
// expiresIn: 3600 // 3600秒(1小时)
});
// 或者使用 exp 声明
const exp = Math.floor(Date.now() / 1000) + (60 * 60); // 1小时后过期
const token = jwt.sign({
...payload,
exp
}, 'secret');
```
### 2. 推荐的过期时间策略
- **Access Token**: 15-30 分钟
- **Refresh Token**: 7-30 天
- **Remember Me Token**: 30-90 天
## Refresh Token 机制实现
### 1. 双 Token 架构
```javascript
const crypto = require('crypto');
const jwt = require('jsonwebtoken');
// 存储 Refresh Token(生产环境使用 Redis)
const refreshTokens = new Map();
// 生成 Access Token
function generateAccessToken(userId) {
return jwt.sign(
{ userId },
process.env.JWT_SECRET,
{
expiresIn: '15m',
issuer: 'your-app.com',
audience: 'your-api'
}
);
}
// 生成 Refresh Token
function generateRefreshToken() {
return crypto.randomBytes(40).toString('hex');
}
// 登录接口
app.post('/auth/login', async (req, res) => {
const { username, password } = req.body;
// 验证用户凭据
const user = await validateUser(username, password);
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// 生成 tokens
const accessToken = generateAccessToken(user.id);
const refreshToken = generateRefreshToken();
// 存储 refresh token(带过期时间)
refreshTokens.set(refreshToken, {
userId: user.id,
expiresAt: Date.now() + (7 * 24 * 60 * 60 * 1000) // 7天
});
res.json({
accessToken,
refreshToken,
expiresIn: 900 // 15分钟(秒)
});
});
// 刷新 token 接口
app.post('/auth/refresh', (req, res) => {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(400).json({ error: 'Refresh token required' });
}
const tokenData = refreshTokens.get(refreshToken);
if (!tokenData) {
return res.status(401).json({ error: 'Invalid refresh token' });
}
// 检查是否过期
if (Date.now() > tokenData.expiresAt) {
refreshTokens.delete(refreshToken);
return res.status(401).json({ error: 'Refresh token expired' });
}
// 生成新的 access token
const newAccessToken = generateAccessToken(tokenData.userId);
res.json({
accessToken: newAccessToken,
expiresIn: 900
});
});
// 登出接口
app.post('/auth/logout', (req, res) => {
const { refreshToken } = req.body;
refreshTokens.delete(refreshToken);
res.json({ success: true });
});
```
### 2. Redis 存储实现(推荐)
```javascript
const redis = require('redis');
const client = redis.createClient();
async function storeRefreshToken(refreshToken, userId, expiresIn = '7d') {
await client.setex(
`refresh:${refreshToken}`,
60 * 60 * 24 * 7, // 7天(秒)
userId
);
}
async function getUserIdFromRefreshToken(refreshToken) {
const userId = await client.get(`refresh:${refreshToken}`);
return userId;
}
async function deleteRefreshToken(refreshToken) {
await client.del(`refresh:${refreshToken}`);
}
```
## 自动刷新机制
### 1. 前端自动刷新
```javascript
// 使用 Axios 拦截器
import axios from 'axios';
const api = axios.create({
baseURL: 'https://api.example.com'
});
let isRefreshing = false;
let refreshSubscribers = [];
function subscribeTokenRefresh(callback) {
refreshSubscribers.push(callback);
}
function onRefreshed(token) {
refreshSubscribers.forEach(callback => callback(token));
refreshSubscribers = [];
}
api.interceptors.request.use(config => {
const token = localStorage.getItem('accessToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
api.interceptors.response.use(
response => response,
async error => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
return new Promise(resolve => {
subscribeTokenRefresh(token => {
originalRequest.headers.Authorization = `Bearer ${token}`;
resolve(api(originalRequest));
});
});
}
originalRequest._retry = true;
isRefreshing = true;
try {
const refreshToken = localStorage.getItem('refreshToken');
const response = await axios.post('/auth/refresh', { refreshToken });
const { accessToken } = response.data;
localStorage.setItem('accessToken', accessToken);
onRefreshed(accessToken);
isRefreshing = false;
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
return api(originalRequest);
} catch (refreshError) {
isRefreshing = false;
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
window.location.href = '/login';
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
```
### 2. 定时刷新
```javascript
// 在 token 过期前 5 分钟刷新
function scheduleTokenRefresh() {
const token = localStorage.getItem('accessToken');
if (!token) return;
const decoded = jwt.decode(token);
const expiresIn = decoded.exp * 1000 - Date.now();
const refreshBefore = 5 * 60 * 1000; // 5分钟
if (expiresIn > refreshBefore) {
setTimeout(async () => {
try {
const refreshToken = localStorage.getItem('refreshToken');
const response = await axios.post('/auth/refresh', { refreshToken });
localStorage.setItem('accessToken', response.data.accessToken);
scheduleTokenRefresh(); // 继续调度
} catch (error) {
console.error('Token refresh failed', error);
}
}, expiresIn - refreshBefore);
}
}
// 登录成功后启动定时刷新
scheduleTokenRefresh();
```
## 高级特性
### 1. Token 黑名单
```javascript
const redis = require('redis');
const client = redis.createClient();
// 登出时将 token 加入黑名单
async function logout(token) {
const decoded = jwt.decode(token);
const ttl = decoded.exp - Math.floor(Date.now() / 1000);
if (ttl > 0) {
await client.setex(`blacklist:${token}`, ttl, '1');
}
}
// 验证时检查黑名单
async function verifyToken(token) {
const isBlacklisted = await client.exists(`blacklist:${token}`);
if (isBlacklisted) {
throw new Error('Token is blacklisted');
}
return jwt.verify(token, process.env.JWT_SECRET);
}
```
### 2. Token 版本控制
```javascript
// 在用户表中维护 tokenVersion 字段
function generateAccessToken(userId, tokenVersion) {
return jwt.sign(
{ userId, tokenVersion },
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
}
// 验证时检查版本
async function verifyToken(token) {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await getUserById(decoded.userId);
if (user.tokenVersion !== decoded.tokenVersion) {
throw new Error('Token version mismatch');
}
return decoded;
}
// 强制登出所有设备(增加版本号)
async function revokeAllTokens(userId) {
await incrementTokenVersion(userId);
}
```
### 3. 滑动过期时间
```javascript
// 每次使用 refresh token 时延长其有效期
app.post('/auth/refresh', async (req, res) => {
const { refreshToken } = req.body;
const tokenData = await getRefreshTokenData(refreshToken);
// 延长 refresh token 有效期
await extendRefreshTokenExpiry(refreshToken, 7 * 24 * 60 * 60); // 7天
const newAccessToken = generateAccessToken(tokenData.userId);
res.json({
accessToken: newAccessToken,
expiresIn: 900
});
});
```
## 最佳实践
1. **使用短期 Access Token**: 15-30 分钟
2. **使用长期 Refresh Token**: 7-30 天
3. **Refresh Token 存储在 HttpOnly Cookie**: 防止 XSS
4. **实现 Token 黑名单**: 支持主动撤销
5. **添加 Token 版本控制**: 支持强制登出
6. **记录 Token 使用日志**: 监控异常行为
7. **限制 Refresh Token 使用次数**: 防止滥用
8. **使用 HTTPS 传输**: 防止中间人攻击
通过合理的过期时间管理和刷新机制,可以在安全性和用户体验之间取得良好平衡。
服务端 · 2026年2月21日 17:52