const jwt = require('jsonwebtoken'); const User = require('../models/User'); const logger = require('../utils/logger').module('AuthMiddleware'); function getJwtSecret() { const envSecret = process.env.JWT_SECRET; if (process.env.NODE_ENV === 'production') { if (!envSecret) { throw new Error( '[致命错误] 生产环境未设置 JWT_SECRET 环境变量!\n' + '请在服务器环境变量中设置强密钥(至少32位随机字符)。\n' + '生成命令(PowerShell):-join ((48..57) + (65..90) + (97..122) | Get-Random -Count 64 | ForEach-Object { [char]$_ })' ); } if (envSecret.length < 32) { throw new Error( '[致命错误] 生产环境 JWT_SECRET 长度必须至少32位!当前长度:' + envSecret.length ); } return envSecret; } if (!envSecret) { throw new Error('[错误] JWT_SECRET 未配置,请检查 initConfig.js 是否正确执行'); } return envSecret; } const JWT_SECRET = getJwtSecret(); const TOKEN_EXPIRY = process.env.TOKEN_EXPIRY || '24h'; const getBrowserInfo = userAgent => { let device = 'Desktop'; let browser = 'Unknown'; let os = 'Unknown'; if (/Mobile|Android|iPhone|iPad|iPod/i.test(userAgent)) { device = 'Mobile'; if (/iPad/i.test(userAgent)) { device = 'Tablet'; } } if (/Firefox/i.test(userAgent)) { browser = 'Firefox'; } else if (/Chrome/i.test(userAgent)) { browser = 'Chrome'; } else if (/Safari/i.test(userAgent)) { browser = 'Safari'; } else if (/Edge/i.test(userAgent)) { browser = 'Edge'; } else if (/MSIE|Trident/i.test(userAgent)) { browser = 'IE'; } if (/Windows/i.test(userAgent)) { os = 'Windows'; } else if (/Mac OS/i.test(userAgent)) { os = 'macOS'; } else if (/Linux/i.test(userAgent)) { os = 'Linux'; } else if (/Android/i.test(userAgent)) { os = 'Android'; } else if (/iOS|iPhone|iPad|iPod/i.test(userAgent)) { os = 'iOS'; } return { device, browser, os }; }; const generateToken = user => { return jwt.sign( { userId: user.userId, username: user.username, roleId: user.roleId, }, JWT_SECRET, { expiresIn: TOKEN_EXPIRY } ); }; const verifyToken = token => { try { return jwt.verify(token, JWT_SECRET); } catch (error) { return null; } }; const authMiddleware = async (req, res, next) => { try { let token = null; const authHeader = req.headers.authorization; if (authHeader && authHeader.startsWith('Bearer ')) { token = authHeader.substring(7); } if (!token && req.query.token) { token = req.query.token; } if (!token) { return res.status(401).json({ success: false, message: '未提供认证令牌', }); } const decoded = verifyToken(token); if (!decoded) { return res.status(401).json({ success: false, message: '令牌无效或已过期', }); } // 验证用户是否存在,增加详细的错误日志 let user; try { user = await User.findByPk(decoded.userId); } catch (dbError) { // 数据库查询错误 logger.error('数据库查询失败', { userId: decoded.userId, error: dbError.message, stack: dbError.stack, }); return res.status(500).json({ success: false, message: '数据库查询失败,请稍后重试', }); } if (!user) { logger.warn('用户不存在', { userId: decoded.userId, username: decoded.username, }); return res.status(401).json({ success: false, message: '用户不存在', }); } if (user.status === 'locked') { logger.warn('账户已被锁定', { userId: user.userId, username: user.username, }); return res.status(403).json({ success: false, message: '账户已被锁定', }); } if (user.status === 'inactive') { logger.warn('账户已禁用', { userId: user.userId, username: user.username, }); return res.status(403).json({ success: false, message: '账户已禁用', }); } req.user = decoded; req.userModel = user; next(); } catch (error) { // 捕获未预期的错误 logger.error('未预期的错误', { error: error.message, stack: error.stack, url: req?.url, method: req?.method, ip: req?.ip, }); return res.status(500).json({ success: false, message: '认证失败,请稍后重试', }); } }; const optionalAuth = async (req, res, next) => { try { const authHeader = req.headers.authorization; if (authHeader && authHeader.startsWith('Bearer ')) { const token = authHeader.substring(7); const decoded = verifyToken(token); if (decoded) { let user; try { user = await User.findByPk(decoded.userId); } catch (dbError) { // 数据库错误时不中断请求,仅记录日志 logger.warn('可选认证数据库查询失败', { userId: decoded.userId, error: dbError.message, }); // 继续执行,不设置用户信息 next(); return; } if (user && user.status === 'active') { req.user = decoded; req.userModel = user; } } } next(); } catch (error) { // 可选认证失败不影响主流程,仅记录日志 logger.warn('可选认证失败(已忽略)', { error: error.message, url: req?.url, }); next(); } }; module.exports = { generateToken, verifyToken, authMiddleware, optionalAuth, JWT_SECRET, TOKEN_EXPIRY, };