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

面试题手册

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设置合理的过期时间敏感操作需要二次验证
阅读 0·2026年2月21日 17:53

什么是 JWT,它由哪几部分组成

JWT (JSON Web Token) 是一种开放标准 (RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间以 JSON 对象的形式安全地传输信息。JWT 由三个部分组成,通过点(.)分隔:Header (头部):包含两部分信息:令牌类型(通常是 JWT)和使用的签名算法(如 HS256、RS256 等)。Header 是一个 JSON 对象,经过 Base64Url 编码。Payload (载荷):包含声明(Claims),即关于实体(通常是用户)和其他数据的声明。声明分为三类:注册声明(Registered Claims):如 iss(签发者)、exp(过期时间)、sub(主题)、aud(受众)等公共声明(Public Claims):可以自定义,但应避免冲突私有声明(Private Claims):在同意使用它们的各方之间共享信息的自定义声明Payload 也是 JSON 对象,经过 Base64Url 编码。Signature (签名):用于验证消息在传递过程中未被篡改。签名是通过将编码后的 Header 和 Payload 用点连接,然后使用 Header 中指定的算法和密钥进行签名生成的。JWT 的完整格式为:Header.Payload.Signature例如:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cJWT 的主要特点:紧凑:可以通过 URL、POST 参数或 HTTP header 发送自包含:包含所有必要信息,减少数据库查询跨语言支持:多种编程语言都有实现无状态:服务器不需要存储会话信息JWT 常用于身份验证和信息交换,特别适合分布式系统的单点登录(SSO)场景。
阅读 0·2026年2月21日 17:53

JWT 和 OAuth2.0 有什么区别

JWT 和 OAuth2.0 是两个不同的概念,经常被混淆。以下是它们的区别和关系:JWT (JSON Web Token)定义JWT 是一种令牌格式标准(RFC 7519),定义了一种紧凑且自包含的方式,用于在各方之间以 JSON 对象的形式安全地传输信息。特点格式标准: 定义了 token 的结构和编码方式自包含: 包含所有必要信息,无需查询数据库无状态: 服务器不需要存储会话信息跨语言: 多种编程语言都有实现用途身份验证信息交换单点登录(SSO)示例eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cOAuth2.0定义OAuth2.0 是一个授权框架(RFC 6749),定义了授权流程和规范,允许用户授权第三方应用访问其在另一个服务上的资源,而无需共享密码。特点授权框架: 定义了授权流程和角色委托授权: 用户授权第三方应用访问资源令牌机制: 使用 Access Token 访问资源多种授权模式: 支持授权码、隐式、密码、客户端凭证等模式角色Resource Owner: 资源所有者(用户)Client: 第三方应用Authorization Server: 授权服务器Resource Server: 资源服务器授权模式Authorization Code: 最安全,推荐用于有后端的应用Implicit: 简单但不安全,用于纯前端应用Resource Owner Password Credentials: 用户名密码,信任的应用Client Credentials: 服务对服务,不需要用户参与示例流程用户 → 第三方应用 → 授权服务器 → 资源服务器JWT 与 OAuth2.0 的关系关键点JWT 是一种令牌格式,OAuth2.0 是一个授权框架它们可以结合使用OAuth2.0 可以使用 JWT 作为 Access Token 的格式OAuth2.0 的授权流程可以返回 JWT 格式的 tokenJWT 可以携带 OAuth2.0 的声明信息示例: OAuth2.0 使用 JWT// 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/resourceAuthorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...对比总结| 特性 | JWT | OAuth2.0 ||------|-----|----------|| 类型 | 令牌格式标准 | 授权框架 || 目的 | 安全传输信息 | 授权第三方访问资源 || 状态 | 无状态 | 可以有状态 || 存储 | 客户端存储 | 服务器可存储 || 撤销 | 困难 | 较容易 || 标准化 | RFC 7519 | RFC 6749 || 独立性 | 可独立使用 | 需要配合使用 |实际应用场景1. 使用 JWT 的场景// 简单的身份验证const token = jwt.sign({ userId: '123' }, SECRET_KEY);// 验证const decoded = jwt.verify(token, SECRET_KEY);2. 使用 OAuth2.0 的场景// 第三方登录(如 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// OAuth2.0 授权服务器签发 JWTapp.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 });});// 资源服务器验证 JWTapp.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 可以根据需求选择合适的授权模式,不一定复杂。最佳实践理解需求: 明确是需要令牌格式还是授权框架选择合适的技术: 根据场景选择 JWT、OAuth2.0 或两者结合安全实现: 无论使用哪种技术,都要注意安全遵循标准: 遵循 RFC 标准和最佳实践测试验证: 充分测试认证和授权流程通过理解 JWT 和 OAuth2.0 的区别和关系,可以在实际项目中做出正确的技术选择。
阅读 0·2026年2月21日 17:53

JWT 在实际应用中会遇到哪些常见问题,如何解决

JWT 在实际应用中会面临各种常见问题,以下是这些问题及其解决方案:1. Token 泄露问题问题JWT 存储在客户端,可能通过 XSS 攻击被窃取。解决方案// 使用 HttpOnly Cookie 存储 JWTapp.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 一旦签发,在过期前无法主动撤销。解决方案// 使用 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 过期时间过长会增加安全风险。解决方案// 使用短期 Access Token + 长期 Refresh Tokenconst 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 编码,任何人都可以解码查看。解决方案// 不存储敏感信息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 问题。解决方案// 服务器端配置 CORSconst 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 体积过大,影响传输性能。解决方案// 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。解决方案// 为每个设备生成独立的 refresh tokenapp.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 签名,影响性能。解决方案// 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. 密钥管理问题问题密钥管理不当会导致严重安全问题。解决方案// 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 的使用情况,发现异常行为。解决方案// 记录 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'); }}最佳实践总结使用 HttpOnly Cookie 存储 JWT实现 Token 黑名单 支持主动撤销使用短期 Access Token + 长期 Refresh Token不在 Payload 中存储敏感信息正确配置 CORS 支持跨域优化 Token 大小 提高性能实现多设备管理使用缓存提升性能安全地管理密钥实施审计和监控通过解决这些常见问题,可以构建更安全、更可靠的 JWT 认证系统。
阅读 0·2026年2月21日 17:53

JWT 如何满足合规性要求

JWT 的合规性对于企业级应用和受监管行业非常重要。以下是主要的合规要求和实现方法:1. GDPR 合规数据最小化原则// ❌ 不符合 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);数据删除权// 实现用户数据删除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 };}数据访问权// 实现数据导出功能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)处理// ❌ 不符合 HIPAA: 在 JWT 中存储 PHIconst token = jwt.sign({ userId: '123', patientName: 'John Doe', diagnosis: 'Hypertension', medications: ['Lisinopril', 'Amlodipine']}, SECRET_KEY);// ✅ 符合 HIPAA: 只存储引用 IDconst token = jwt.sign({ sub: '123', scope: 'patient.read', aud: 'healthcare-api'}, SECRET_KEY);// PHI 存储在安全的数据库中const patientData = await db.get('patients', { id: '123' });审计日志// 记录所有 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);});最小权限原则// 实现细粒度权限控制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 合规不存储敏感信息// ❌ 不符合 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);加密传输// 强制使用 HTTPSconst 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();});安全的密钥管理// 使用 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 合规访问控制// 实现基于角色的访问控制(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(); };}审计追踪// 全面的审计日志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);});变更管理// 记录配置变更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 合规信息安全策略// 实现安全策略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;}风险评估// 实现风险评估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. 最佳实践合规性最佳实践了解适用的法规: 确定哪些合规要求适用于你的业务实施最小权限: 只授予必要的访问权限记录所有活动: 维护完整的审计日志定期审计: 定期检查合规性培训员工: 确保员工了解合规要求使用加密: 保护敏感数据实施访问控制: 限制对敏感信息的访问建立事件响应: 准备应对安全事件定期更新: 保持系统和策略的最新寻求专业建议: 必要时咨询合规专家通过遵循这些合规要求,可以确保 JWT 认证系统符合各种法规和标准。
阅读 0·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 配置{ "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 中传递 JWT5. 存储安全优先使用 HttpOnly Cookie如使用 LocalStorage,确保防范 XSS实现 Token 自动刷新机制6. 监控和审计记录 Token 的签发、验证、刷新操作监控异常的 Token 使用模式实现速率限制,防止暴力破解安全检查清单[ ] 使用强签名算法(RS256 或 ES256)[ ] 设置合理的过期时间[ ] 使用 HTTPS 传输[ ] 不存储敏感信息在 Payload 中[ ] 实现 Token 刷新机制[ ] 添加 jti 声明防止重放[ ] 使用 HttpOnly Cookie 存储[ ] 实现 Token 黑名单[ ] 密钥安全存储和定期轮换[ ] 监控和日志记录通过以上措施,可以大大提高 JWT 的安全性。
阅读 0·2026年2月21日 17:52

如何优化 JWT 的性能

JWT 的性能优化对于高并发系统非常重要。以下是主要的优化策略和实现方法:1. 签名算法优化选择更快的算法// 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. 缓存优化验证结果缓存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: '...' });});公钥缓存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 大小// ❌ 不好的做法:字段名过长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);使用压缩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// ❌ 同步验证(阻塞)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. 批量验证批量验证多个 Tokenfunction 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. 数据库优化减少数据库查询// ❌ 每次请求都查询数据库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. 连接池优化使用连接池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. 负载均衡多实例部署// 使用 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. 监控和调优性能监控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[ ] 减少数据库查询[ ] 使用连接池[ ] 实现负载均衡[ ] 添加性能监控[ ] 定期分析和优化性能基准测试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 认证系统的性能,支持更高的并发量。
阅读 0·2026年2月21日 17:52

如何对 JWT 进行测试

JWT 的测试是确保认证系统安全可靠的重要环节。以下是完整的测试策略和实现方法:1. 单元测试测试 Token 生成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 验证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. 集成测试测试认证流程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 刷新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. 安全测试测试算法混淆攻击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 篡改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 生成性能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 测试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// 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// cypress/integration/auth.spec.jsdescribe('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 认证系统的安全性、可靠性和性能。
阅读 0·2026年2月21日 17:52

JWT 的签名算法有哪些,如何选择

JWT 的签名算法是保证其安全性的关键,以下是主要签名算法的对比和选择建议:主要签名算法1. HS256 (HMAC SHA256)对称加密算法,使用相同的密钥进行签名和验证。特点:性能较好,计算速度快密钥管理复杂,需要安全地共享密钥适合单服务应用示例:const token = jwt.sign(payload, 'secret-key', { algorithm: 'HS256' });const decoded = jwt.verify(token, 'secret-key', { algorithms: ['HS256'] });适用场景:单体应用服务端对服务端的认证内部系统通信2. RS256 (RSA SHA256)非对称加密算法,使用私钥签名,公钥验证。特点:安全性更高,私钥不泄露公钥可以公开分发密钥管理相对简单性能较慢示例: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 更好安全性高密钥生成速度快示例: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 密钥对# 生成私钥openssl genrsa -out private.key 2048# 生成公钥openssl rsa -in private.key -pubout -out public.key生成 ECDSA 密钥对# 生成私钥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 的情况金融、医疗等高安全要求领域需要最强的安全性证明不介意性能开销安全注意事项永远不要使用 none 算法 // 危险!不要这样做 const token = jwt.sign(payload, '', { algorithm: 'none' });验证时指定算法 // 安全做法 const decoded = jwt.verify(token, key, { algorithms: ['RS256'] // 明确指定允许的算法 });使用足够强度的密钥RSA: 至少 2048 位,推荐 3072 或 4096 位ECDSA: 至少 P-256,推荐 P-384 或 P-521HMAC: 至少 256 位定期轮换密钥建议每 6-12 个月轮换一次实现密钥版本管理支持多密钥验证(平滑过渡)安全存储密钥使用环境变量或密钥管理服务不要将密钥提交到代码仓库限制密钥的访问权限性能优化建议缓存公钥: 验证时缓存公钥,避免重复读取使用 ES256: 在安全性和性能之间取得平衡批量验证: 对于大量 token,考虑批量验证异步验证: 使用异步 API 避免阻塞通过合理选择签名算法,可以在安全性、性能和易用性之间找到最佳平衡。
阅读 0·2026年2月21日 17:52

JWT 的过期时间如何设置,如何实现刷新机制

JWT 的过期时间管理和刷新机制是保证安全性和用户体验的重要部分。以下是详细的实现方案:过期时间设置1. 基本过期时间设置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 架构const crypto = require('crypto');const jwt = require('jsonwebtoken');// 存储 Refresh Token(生产环境使用 Redis)const refreshTokens = new Map();// 生成 Access Tokenfunction generateAccessToken(userId) { return jwt.sign( { userId }, process.env.JWT_SECRET, { expiresIn: '15m', issuer: 'your-app.com', audience: 'your-api' } );}// 生成 Refresh Tokenfunction 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 存储实现(推荐)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. 前端自动刷新// 使用 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. 定时刷新// 在 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 黑名单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 版本控制// 在用户表中维护 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. 滑动过期时间// 每次使用 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 });});最佳实践使用短期 Access Token: 15-30 分钟使用长期 Refresh Token: 7-30 天Refresh Token 存储在 HttpOnly Cookie: 防止 XSS实现 Token 黑名单: 支持主动撤销添加 Token 版本控制: 支持强制登出记录 Token 使用日志: 监控异常行为限制 Refresh Token 使用次数: 防止滥用使用 HTTPS 传输: 防止中间人攻击通过合理的过期时间管理和刷新机制,可以在安全性和用户体验之间取得良好平衡。
阅读 0·2026年2月21日 17:52