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

JWT 的错误处理应该如何实现

2月21日 17:52

JWT 的错误处理是构建健壮认证系统的关键。以下是完整的错误处理策略和实现方法:

常见 JWT 错误类型

1. Token 格式错误

javascript
const jwt = require('jsonwebtoken'); function verifyToken(token) { try { return jwt.verify(token, SECRET_KEY); } catch (error) { if (error.name === 'JsonWebTokenError') { throw new Error('Invalid token format'); } throw error; } }

2. Token 过期错误

javascript
function verifyToken(token) { try { return jwt.verify(token, SECRET_KEY); } catch (error) { if (error.name === 'TokenExpiredError') { throw new Error('Token has expired'); } throw error; } }

3. 签名验证错误

javascript
function verifyToken(token) { try { return jwt.verify(token, SECRET_KEY); } catch (error) { if (error.message === 'invalid signature') { throw new Error('Invalid token signature'); } throw error; } }

4. 算法错误

javascript
function verifyToken(token) { try { return jwt.verify(token, SECRET_KEY, { algorithms: ['RS256'] }); } catch (error) { if (error.message === 'invalid algorithm') { throw new Error('Invalid token algorithm'); } throw error; } }

统一错误处理中间件

Express 错误处理中间件

javascript
const jwt = require('jsonwebtoken'); // 认证中间件 function authMiddleware(req, res, next) { const authHeader = req.headers['authorization']; if (!authHeader) { return res.status(401).json({ error: 'Unauthorized', message: 'No token provided' }); } const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : authHeader; try { const decoded = jwt.verify(token, process.env.JWT_SECRET); req.user = decoded; next(); } catch (error) { handleJwtError(error, res); } } // JWT 错误处理函数 function handleJwtError(error, res) { const errorMap = { 'TokenExpiredError': { status: 401, code: 'TOKEN_EXPIRED', message: 'Token has expired' }, 'JsonWebTokenError': { status: 401, code: 'INVALID_TOKEN', message: 'Invalid token' }, 'NotBeforeError': { status: 401, code: 'TOKEN_NOT_ACTIVE', message: 'Token is not active yet' } }; const errorInfo = errorMap[error.name] || { status: 500, code: 'AUTH_ERROR', message: 'Authentication error' }; res.status(errorInfo.status).json({ error: errorInfo.code, message: errorInfo.message, timestamp: new Date().toISOString() }); } // 全局错误处理中间件 app.use((err, req, res, next) => { console.error('Error:', err); if (err.name === 'JsonWebTokenError' || err.name === 'TokenExpiredError' || err.name === 'NotBeforeError') { return handleJwtError(err, res); } res.status(500).json({ error: 'INTERNAL_SERVER_ERROR', message: 'An unexpected error occurred' }); });

错误日志记录

结构化日志记录

javascript
const winston = require('winston'); const logger = winston.createLogger({ level: 'info', format: winston.format.combine( winston.format.timestamp(), winston.format.json() ), transports: [ new winston.transports.Console(), new winston.transports.File({ filename: 'auth-errors.log' }) ] }); function logAuthError(error, req) { logger.error({ error: error.name, message: error.message, stack: error.stack, timestamp: new Date().toISOString(), ip: req.ip, userAgent: req.headers['user-agent'], path: req.path, method: req.method }); } // 使用示例 function authMiddleware(req, res, next) { try { const decoded = jwt.verify(token, SECRET_KEY); req.user = decoded; next(); } catch (error) { logAuthError(error, req); handleJwtError(error, res); } }

Token 刷新错误处理

刷新 Token 的错误处理

javascript
async function refreshToken(refreshToken) { try { // 验证 refresh token const decoded = jwt.verify(refreshToken, REFRESH_SECRET); // 检查是否在黑名单中 const isBlacklisted = await checkBlacklist(refreshToken); if (isBlacklisted) { throw new Error('Token has been revoked'); } // 生成新的 access token const accessToken = jwt.sign( { userId: decoded.userId }, ACCESS_SECRET, { expiresIn: '15m' } ); return { accessToken, expiresIn: 900 }; } catch (error) { throw handleRefreshError(error); } } function handleRefreshError(error) { const errorMap = { 'TokenExpiredError': { code: 'REFRESH_TOKEN_EXPIRED', message: 'Refresh token has expired, please login again', action: 'LOGIN_REQUIRED' }, 'JsonWebTokenError': { code: 'INVALID_REFRESH_TOKEN', message: 'Invalid refresh token', action: 'LOGIN_REQUIRED' }, 'TokenRevokedError': { code: 'TOKEN_REVOKED', message: 'Token has been revoked', action: 'LOGIN_REQUIRED' } }; const errorInfo = errorMap[error.name] || { code: 'REFRESH_ERROR', message: 'Failed to refresh token', action: 'RETRY' }; return { ...errorInfo, originalError: error.message }; }

前端错误处理

Axios 拦截器

javascript
import axios from 'axios'; const api = axios.create({ baseURL: 'https://api.example.com' }); // 响应拦截器 api.interceptors.response.use( response => response, async error => { const originalRequest = error.config; // 处理 401 错误 if (error.response?.status === 401 && !originalRequest._retry) { originalRequest._retry = true; const errorCode = error.response.data?.error; // Token 过期,尝试刷新 if (errorCode === 'TOKEN_EXPIRED') { try { const refreshToken = localStorage.getItem('refreshToken'); const response = await axios.post('/auth/refresh', { refreshToken }); const { accessToken } = response.data; localStorage.setItem('accessToken', accessToken); // 重试原请求 originalRequest.headers.Authorization = `Bearer ${accessToken}`; return api(originalRequest); } catch (refreshError) { // 刷新失败,跳转登录 localStorage.removeItem('accessToken'); localStorage.removeItem('refreshToken'); window.location.href = '/login'; return Promise.reject(refreshError); } } // 其他 401 错误,跳转登录 localStorage.removeItem('accessToken'); localStorage.removeItem('refreshToken'); window.location.href = '/login'; } // 处理其他错误 return Promise.reject(error); } );

错误监控和告警

Sentry 集成

javascript
const Sentry = require('@sentry/node'); Sentry.init({ dsn: process.env.SENTRY_DSN, environment: process.env.NODE_ENV }); function authMiddleware(req, res, next) { try { const decoded = jwt.verify(token, SECRET_KEY); req.user = decoded; next(); } catch (error) { // 发送错误到 Sentry Sentry.captureException(error, { tags: { errorType: error.name, endpoint: req.path }, extra: { ip: req.ip, userAgent: req.headers['user-agent'] } }); handleJwtError(error, res); } }

自定义错误类

创建自定义错误

javascript
class AuthError extends Error { constructor(code, message, statusCode = 401) { super(message); this.name = 'AuthError'; this.code = code; this.statusCode = statusCode; } } class TokenExpiredError extends AuthError { constructor() { super('TOKEN_EXPIRED', 'Token has expired', 401); } } class InvalidTokenError extends AuthError { constructor() { super('INVALID_TOKEN', 'Invalid token', 401); } } class TokenRevokedError extends AuthError { constructor() { super('TOKEN_REVOKED', 'Token has been revoked', 401); } } // 使用自定义错误 function verifyToken(token) { try { return jwt.verify(token, SECRET_KEY); } catch (error) { if (error.name === 'TokenExpiredError') { throw new TokenExpiredError(); } if (error.name === 'JsonWebTokenError') { throw new InvalidTokenError(); } throw new AuthError('AUTH_ERROR', 'Authentication failed'); } }

错误恢复策略

重试机制

javascript
async function verifyTokenWithRetry(token, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return jwt.verify(token, SECRET_KEY); } catch (error) { // 只重试临时性错误 if (error.name === 'TokenExpiredError') { throw error; // 不重试过期错误 } if (i === maxRetries - 1) { throw error; } // 等待后重试 await new Promise(resolve => setTimeout(resolve, 100 * (i + 1))); } } }

最佳实践

错误处理清单

  • 统一错误格式
  • 记录详细错误日志
  • 区分不同错误类型
  • 提供友好的错误消息
  • 实现错误监控
  • 设置错误告警
  • 处理前端错误
  • 实现重试机制
  • 使用自定义错误类
  • 定期分析错误日志

错误处理原则

  1. 不要暴露敏感信息: 错误消息不应包含密钥或内部实现细节
  2. 记录完整上下文: 日志应包含请求信息、用户信息等
  3. 提供清晰反馈: 用户应知道发生了什么以及如何解决
  4. 监控关键错误: 对重要错误设置告警
  5. 定期分析日志: 发现潜在问题和攻击模式

通过完善的错误处理机制,可以提升 JWT 认证系统的健壮性和用户体验。

标签:JWT