You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Express+Multer上传权限预校验:如何提前校验分类与用户权限

解决Express认证中间件无法获取Multer解析的FormData分类字段问题

我明白你的困扰:你想在认证授权中间件里提前校验用户上传的分类权限,但因为Multer的解析时机问题,中间件里拿不到FormData里的category字段,只能等到上传处理函数里才能拿到,导致无法提前拦截非法请求。

问题根源

Multer默认是在路由处理函数中调用upload(req, res)时才会解析FormData。而你的认证中间件是在路由处理函数之前执行的,此时FormData还没被解析,所以req.body里自然没有category这个字段——这就是为什么你在AuthController里只能看到用户认证信息,看不到分类ID的原因。

最佳解决方案:在Multer的fileFilter中做权限校验

fileFilter是Multer提供的钩子函数,它会在文件开始上传前执行,并且此时Multer已经解析了FormData中的文本字段(包括category)。同时,你可以通过认证中间件把用户信息(比如isAdmin)挂载到req.user上(这比存在req.body里安全得多,避免伪造),这样就能在fileFilter里同时拿到用户权限和分类信息,提前拦截非法请求,还能避免无效文件上传。

步骤1:修正认证中间件,将用户信息挂载到req.user

首先,把用户的认证信息(比如isAdmin)从req.body转移到req.user——这是Express认证的标准做法,避免客户端篡改:

// AuthController.ts
import { Request, Response, NextFunction } from 'express';
import { Unauthorized } from './your-error-classes';

public static authenticate(req: Request, res: Response, next: NextFunction) {
  // 这里替换成你的实际认证逻辑(比如JWT解析、会话获取)
  // 示例:从安全渠道获取用户信息,而非req.body
  const user = { user_id: 1, isAdmin: true }; 
  req.user = user; // 将用户信息挂载到req.user
  next();
}

public static allowAuthenticated() {
  return (req: Request, res: Response, next: NextFunction) => {
    if (!req.user) {
      return res.status(401).send(new Unauthorized());
    }
    next();
  };
}

步骤2:在fileFilter中加入权限校验

修改你的imageFilter,同时校验文件类型和上传权限:

// UploadController.ts
import { Request } from 'express';
import multer, { Multer } from 'multer';
import { Unauthorized } from './your-error-classes';

const imageFilter = (req: Request, file: Express.Multer.File, cb: Multer.FileFilterCallback) => {
  const user = req.user;
  const category = req.body.category;

  // 权限校验:普通用户只能上传到users分类
  if (!user.isAdmin && category !== 'users') {
    return cb(new Unauthorized('You are not allowed to upload to this category'), false);
  }

  // 文件类型校验
  if (!file.originalname.match(/\.(jpg|jpeg|png|gif)$/)) {
    return cb(new Error('Only image files are allowed!'), false);
  }

  cb(null, true);
};

// 配置Multer,直接作为中间件使用,而非在处理函数里调用
const upload = multer({ 
  storage: yourDiskStorageConfig, // 你的diskStorage配置
  fileFilter: imageFilter, 
  limits: { fileSize: MAX_SIZE } 
}).single('photo');

步骤3:调整路由顺序,先认证再用Multer中间件

把Multer作为路由中间件放在认证之后,这样fileFilter就能拿到req.user和req.body.category:

// 路由配置
router.post('/upload', 
  AuthController.authenticate, // 先认证,挂载req.user
  AuthController.allowAuthenticated(), // 确保用户已登录
  upload, // Multer中间件,执行fileFilter和文件上传
  UploadController.uploadPhoto
);

步骤4:简化UploadController的处理函数

因为权限和文件类型已经在fileFilter里校验过了,处理函数只需要生成返回URL即可:

export class UploadController {
  public static async uploadPhoto(req: Request, res: Response, next: NextFunction) {
    try {
      if (!req.file) {
        return res.status(400).send(new BadRequest('No file uploaded'));
      }
      const imageUrl = `${HOST}/static/${req.file.filename}`;
      res.status(200).json({ imageUrl });
    } catch (e) {
      return res.status(400).send(new BadRequest('Image upload operation is not successful.'));
    }
  }
}

备选方案:提前解析FormData再校验权限

如果你坚持要在认证中间件里做校验,可以先让Multer解析FormData,再执行权限校验,但这样会先上传文件,若权限不通过需要手动删除文件:

// 路由配置:先解析文件和FormData,再认证校验
router.post('/upload',
  upload, 
  AuthController.validateUploadCategory, // 自定义权限校验中间件
  UploadController.uploadPhoto
);

// AuthController.ts新增校验中间件
public static validateUploadCategory(req: Request, res: Response, next: NextFunction) {
  const user = req.user;
  const category = req.body.category;

  if (!user.isAdmin && category !== 'users') {
    // 删除已上传的文件
    fs.unlinkSync(req.file.path);
    return res.status(403).send(new Unauthorized());
  }
  next();
}

这种方式的缺点是会产生无效的文件上传,需要额外处理文件删除,不如第一种方案优雅。

重要提醒

永远不要把isAdmin这样的权限字段放在req.body里!客户端可以轻易伪造这个字段,导致权限绕过。正确的做法是通过JWT、会话等方式从服务器端获取用户的权限信息,然后挂载到req.user上。

内容的提问来源于stack exchange,提问作者gokcand

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:16:47