/** * 身份认证和权限验证中间件 * * 功能: * 1. 验证用户是否已登录(JWT token验证) * 2. 验证用户是否有足够的权限访问资源 */ const { verifyToken } = require('../utils/encryption'); const { AuthenticationError, AuthorizationError } = require('./errorHandler'); /** * 身份认证中间件 * 验证用户是否已登录(通过JWT token) * * 说明: * - 前端在请求头中携带 token: "Bearer xxxxx" * - 服务器验证token是否有效 * - 验证通过后,将用户信息存入 req.user * * 使用示例: * router.get('/profile', authenticate, getProfile); */ const authenticate = async (req, res, next) => { try { // 从请求头获取token const authHeader = req.headers.authorization; // 检查是否提供了token if (!authHeader || !authHeader.startsWith('Bearer ')) { throw new AuthenticationError('未提供认证令牌,请先登录'); } // 提取token(去掉"Bearer "前缀) const token = authHeader.substring(7); // 验证token const decoded = verifyToken(token); // 将解密后的用户信息存入req对象,供后续中间件使用 req.user = { id: decoded.id, username: decoded.username, role: decoded.role }; // 继续执行下一个中间件 next(); } catch (error) { // 如果是我们定义的错误,直接传递 if (error instanceof AuthenticationError) { next(error); } else { // 其他错误统一处理为认证失败 next(new AuthenticationError(error.message)); } } }; /** * 角色权限验证中间件工厂函数 * 生成一个验证特定角色权限的中间件 * * @param {array} allowedRoles - 允许访问的角色列表 * @returns {function} Express中间件 * * 说明: * - 必须在 authenticate 之后使用 * - 检查用户角色是否在允许的角色列表中 * * 使用示例: * // 只允许管理员访问 * router.delete('/users/:id', authenticate, checkRole(['admin']), deleteUser); * * // 允许管理员和服务员访问 * router.post('/orders', authenticate, checkRole(['admin', 'waiter']), createOrder); */ const checkRole = (allowedRoles) => { return (req, res, next) => { try { // 确保已经通过身份认证 if (!req.user) { throw new AuthenticationError('请先登录'); } // 检查用户角色是否在允许列表中 if (!allowedRoles.includes(req.user.role)) { throw new AuthorizationError(`此操作需要以下角色之一: ${allowedRoles.join(', ')}`); } // 权限验证通过,继续执行 next(); } catch (error) { next(error); } }; }; /** * 可选的身份认证中间件 * 如果提供了token就验证,没提供也不报错 * * 说明:适用于"登录后有额外功能,不登录也能访问"的场景 * 例如:浏览菜单不需要登录,但登录后可以看到会员价格 * * 使用示例: * router.get('/menu', optionalAuthenticate, getMenu); */ const optionalAuthenticate = async (req, res, next) => { try { const authHeader = req.headers.authorization; // 如果没有提供token,直接跳过 if (!authHeader || !authHeader.startsWith('Bearer ')) { req.user = null; return next(); } // 如果提供了token,验证它 const token = authHeader.substring(7); const decoded = verifyToken(token); req.user = { id: decoded.id, username: decoded.username, role: decoded.role }; next(); } catch (error) { // token无效也不报错,只是不设置用户信息 req.user = null; next(); } }; /** * 检查是否是资源所有者或管理员 * 用于保护用户只能操作自己的资源 * * @param {string} userIdParam - URL参数中用户ID的字段名 * @returns {function} Express中间件 * * 使用示例: * // 用户只能查看自己的订单,或者管理员可以查看所有订单 * router.get('/users/:userId/orders', authenticate, checkOwnerOrAdmin('userId'), getOrders); */ const checkOwnerOrAdmin = (userIdParam = 'id') => { return (req, res, next) => { try { if (!req.user) { throw new AuthenticationError('请先登录'); } const resourceUserId = parseInt(req.params[userIdParam]); const currentUserId = req.user.id; const isAdmin = req.user.role === 'admin'; // 如果是管理员,或者是资源所有者,允许访问 if (isAdmin || currentUserId === resourceUserId) { next(); } else { throw new AuthorizationError('您只能访问自己的资源'); } } catch (error) { next(error); } }; }; // 导出所有中间件 module.exports = { authenticate, checkRole, optionalAuthenticate, checkOwnerOrAdmin };