乐闻世界logo
搜索文章和话题

JWT

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