NestJS权限守卫无法正确获取FormData数据问题求助
问题:FormData提交时Permission Guard无法获取员工验证数据
系统背景
系统支持两种登录类型:
- 用户登录:标准邮箱密码登录,JWT中包含用户ID;
- 企业登录:员工通过企业ID和密码登录,执行操作时需额外验证员工ID和密码。
当前问题:创建收据时通过FormData提交收据内容、图片及员工验证数据,但Permission Guard无法正确捕获这些数据。
相关代码
PermissionGuard 实现
import { CanActivate, ExecutionContext, ForbiddenException, Injectable, UnauthorizedException, } from '@nestjs/common' import { PrismaService } from '../prisma/prisma.service' import { PermissionEnum } from '@prisma/client' import { Reflector } from '@nestjs/core' import { compare } from 'bcryptjs' @Injectable() export class PermissionGuard implements CanActivate { constructor( private prisma: PrismaService, private reflector: Reflector, ) {} async canActivate(context: ExecutionContext): Promise<boolean> { const requiredPermission = this.reflector.get<PermissionEnum>( 'permission', context.getHandler(), ) if (!requiredPermission) { return true } const request = context.switchToHttp().getRequest() const tokenId = request.user?.sub const isCompany = request.user?.pharmacy const { employeeCode, employeePassword } = request.body if (!tokenId) { throw new UnauthorizedException('User not authenticated') } let permissions: PermissionEnum[] = [] if (isCompany) { const company = await this.prisma.company.findFirst({ where: { id: tokenId }, include: { employees: true, }, }) if (!company) { throw new UnauthorizedException({ statusText: 'unauthorized', message: 'Farmácia não encontrada', }) } const employee = company.employees.find( (employee) => employee.code === employeeCode, ) if (!employee) { throw new UnauthorizedException({ statusText: 'unauthorized', message: 'Funcionário não encontrado', }) } const isPasswordValid = await compare(employeePassword, employee.password) if (!isPasswordValid) { throw new UnauthorizedException({ statusText: 'unauthorized', message: 'Credenciais incorretas', }) } permissions = employee.permissions } else { const user = await this.prisma.user.findFirst({ where: { id: tokenId, }, }) if (!user) { throw new UnauthorizedException({ statusText: 'unauthorized', message: 'User not found', }) } const pharmacy = user?.pharmacies[0].pharmacy if (!pharmacy) { throw new UnauthorizedException({ statusText: 'unauthorized', message: 'Company not encontrada', }) } permissions = user.pharmaceutical.permissions } const hasPermission = permissions.some( (perm) => perm === requiredPermission, ) if (!hasPermission) { throw new ForbiddenException(`Employee does not have permission`) } return true } }
PermissionDecorator 实现
import { SetMetadata } from '@nestjs/common' import { PermissionEnum } from '@prisma/client' export const RequirePermission = (permission: PermissionEnum) => SetMetadata('permission', permission)
创建收据接口实现
@Post() @UseGuards(PermissionGuard) @RequirePermission(PermissionEnum.CREATE_RECEIT) @UseInterceptors( FileFieldsInterceptor([ { name: 'imagem', maxCount: 1 }, { name: 'anexos', maxCount: 5 }, ]), ) async create( @UploadedFiles() files, @Body() body, @CurrentUser() user: TokenPayload, ) { const zodValidationPipe = new ZodValidationPipe(createReceitSchema) const validatedBody = zodValidationPipe.transform(body) const file = files.imagem?.[0] if (!validatedBody?.channel && !file) { throw new BadRequestException('Channel or file is required') } return await this.receitasService.create( file!, validatedBody, ) }
Postman调用情况

解决方案
核心原因
NestJS中守卫(Guard)的执行顺序早于拦截器(Interceptor),当前代码中PermissionGuard先执行,此时FileFieldsInterceptor还未解析FormData,导致request.body为空或未包含FormData中的文本字段,Guard无法获取employeeCode和employeePassword。
解决步骤
调整装饰器顺序:将
@UseInterceptors移到@UseGuards之前,确保拦截器先解析FormData,Guard执行时能拿到完整的请求体数据:@Post() @UseInterceptors( FileFieldsInterceptor([ { name: 'imagem', maxCount: 1 }, { name: 'anexos', maxCount: 5 }, ]), ) @UseGuards(PermissionGuard) @RequirePermission(PermissionEnum.CREATE_RECEIT) async create(...) { ... }验证配置:确认项目中已正确安装multer依赖(NestJS文件上传默认依赖),避免因依赖缺失导致FormData解析失败。
测试验证:修改后重新调用接口,Guard中的
request.body将能正确获取FormData中的employeeCode和employeePassword字段,完成权限验证。
内容的提问来源于stack exchange,提问作者Nilton Schumacher F
相关产品推荐
相关产品推荐

