'use strict'; const jwt = require('jsonwebtoken'); /** * JWT 认证中间件 * * 功能: * 1. 从请求头 Authorization 中提取 Bearer Token * 2. 验证 JWT 的有效性 * 3. 将解码后的用户信息挂载到 ctx.state.currentUser * 4. 支持管理员角色校验(admin 路由专用) * 5. 验证失败返回统一错误响应 * * 使用方式: * - 选手认证:app.middleware.auth() * - 管理员认证:app.middleware.auth({ adminOnly: true }) * * ctx.state.currentUser 数据结构: * { * uid: 账号 uid, * id_number: 身份证号, * role: 'contestant' | 'admin' * } */ module.exports = (options = {}, app) => { return async function authMiddleware(ctx, next) { // 从 ctx.app.config 中获取 JWT 密钥 const appConfig = app || ctx.app; const { secret } = appConfig.config.jwt; // 获取 Authorization 请求头;同时支持 Query ?token= 回退(用于跨设备直链下载, // 如 iOS Safari 不响应 blob + a.download 时改用浏览器原生导航下载的场景)。 const authorization = ctx.get('Authorization') || (ctx.query && ctx.query.token ? `Bearer ${ctx.query.token}` : ''); // 检查 Authorization 头是否存在 if (!authorization) { ctx.status = 401; ctx.body = { code: 401, message: '未提供认证令牌,请先登录', }; return; } // 提取 Bearer Token(格式:Bearer ) const tokenParts = authorization.split(' '); if (tokenParts.length !== 2 || tokenParts[0] !== 'Bearer') { ctx.status = 401; ctx.body = { code: 401, message: '认证令牌格式错误,应为 Bearer ', }; return; } const token = tokenParts[1]; // 验证 JWT Token try { const decoded = jwt.verify(token, secret); // 将解码后的用户信息挂载到 ctx.state.currentUser ctx.state.currentUser = { id: decoded.id || decoded.uid || 0, uid: decoded.uid || decoded.id || 0, id_number: decoded.id_number || '', phone: decoded.phone || '', role: decoded.role, }; // 如果启用了管理员模式,校验当前用户是否为管理员 if (options.adminOnly) { if (ctx.state.currentUser.role !== 'admin') { ctx.status = 403; ctx.body = { code: 403, message: '无权限访问,需要管理员权限', }; return; } } // 继续执行后续中间件和控制器 await next(); } catch (err) { // 仅当错误确实是 JWT 校验问题(过期/签名无效)时,才返回 401「认证失败」。 // 注意:try 内部包含 `await next()`,即后续中间件与控制器也在同一 try 中。 // 若下游控制器/服务抛出异常(如文件读取失败、数据库异常等),绝不能被 // 这里误吞并伪装成 401,否则前端会误判「登录过期」把用户踢回登录页。 // 因此:非 JWT 类错误必须重新抛出,交由 Egg 全局错误处理(返回 500 并写日志)。 if (err.name === 'TokenExpiredError') { // Token 已过期 ctx.status = 401; ctx.body = { code: 401, message: '认证令牌已过期,请重新登录', }; return; } if (err.name === 'JsonWebTokenError') { // Token 无效(签名错误、格式错误等) ctx.status = 401; ctx.body = { code: 401, message: '无效的认证令牌', }; return; } // 其它异常(下游业务逻辑抛出):原样抛出,避免被误报为 401 throw err; } }; };