NestJS如何在Multipart/Form Data中实现角色权限校验?
问题原因
核心问题是NestJS的守卫执行优先级高于Multer拦截器:
- 普通JSON请求的body由Nest默认的
body-parser中间件提前解析,守卫能正常拿到request.body; - 但
Multipart/Form Data需要FileInterceptor解析,而拦截器是在守卫之后、控制器方法执行前运行的。所以当RolesGuard执行时,Form Data的请求体还没被解析,body.role自然是undefined。
解决方案
推荐两种方案,优先选更安全的权限校验逻辑:
方案一:基于登录用户角色校验(更合理)
你的接口中已经通过@GetUser()获取了登录用户信息,说明AuthGuard已经将用户数据注入到request.user中。正常权限校验应该基于登录用户的角色,而非请求体传递的role(避免篡改风险)。
修改RolesGuard代码:
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { Role } from '../enums/role.enum'; import { ROLES_KEY } from '../decorators/roles.decorator'; @Injectable() export class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [ context.getHandler(), context.getClass(), ]); if (!requiredRoles) { return true; } // 从AuthGuard解析后的user对象中取角色,不受请求体格式影响 const { user } = context.switchToHttp().getRequest(); return requiredRoles.some((role) => user.role?.includes(role)); } }
方案二:提前解析Form Data(必须从请求体拿role时用)
通过配置Multer中间件,让Form Data在守卫执行前就完成解析:
1. 在模块中配置Multer中间件
比如在你的CandidatesModule中实现NestModule接口:
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; import { MulterModule, Multer } from '@nestjs/platform-express'; import { diskStorage } from 'multer'; import { extname } from 'path'; import { CandidatesController } from './candidates.controller'; import { CandidatesService } from './candidates.service'; @Module({ imports: [ MulterModule.register({ storage: diskStorage({ destination: './uploads', filename: (req, file, cb) => { const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9); cb(null, `${file.fieldname}-${uniqueSuffix}${extname(file.originalname)}`); }, }), }), ], controllers: [CandidatesController], providers: [CandidatesService], }) export class CandidatesModule implements NestModule { configure(consumer: MiddlewareConsumer) { // 针对目标路由应用Multer中间件,指定文件字段名 consumer .apply(MulterModule.createMiddleware({ dest: './uploads' })) .forRoutes('candidates'); // 替换为你的实际接口路径 } }
2. 调整控制器代码
去掉@UseInterceptors(FileInterceptor('document'))(中间件已完成文件解析),保留其他参数:
@Post() @Roles(Role.Admin) @UseGuards(AuthGuard()) async create( @UploadedFile() document: Express.Multer.File, @Body() createCandidats: CreateCandidats, @GetUser() user: User, @Body('role') role: string ): Promise<Candidate> { // 原有业务逻辑不变 const documentName = document.filename; createCandidats.documents = documentName; return await this.candidateService.create(createCandidats, user); }
此时RolesGuard中的request.body已经被Multer解析,能正常拿到role参数。
内容的提问来源于stack exchange,提问作者Asta
相关产品推荐
相关产品推荐

