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

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调用情况

Postman调用截图


解决方案

核心原因

NestJS中守卫(Guard)的执行顺序早于拦截器(Interceptor),当前代码中PermissionGuard先执行,此时FileFieldsInterceptor还未解析FormData,导致request.body为空或未包含FormData中的文本字段,Guard无法获取employeeCode和employeePassword。

解决步骤

  1. 调整装饰器顺序:将@UseInterceptors移到@UseGuards之前,确保拦截器先解析FormData,Guard执行时能拿到完整的请求体数据:

    @Post()
    @UseInterceptors(
      FileFieldsInterceptor([
        { name: 'imagem', maxCount: 1 },
        { name: 'anexos', maxCount: 5 },
      ]),
    )
    @UseGuards(PermissionGuard)
    @RequirePermission(PermissionEnum.CREATE_RECEIT)
    async create(...) { ... }
    
  2. 验证配置:确认项目中已正确安装multer依赖(NestJS文件上传默认依赖),避免因依赖缺失导致FormData解析失败。

  3. 测试验证:修改后重新调用接口,Guard中的request.body将能正确获取FormData中的employeeCode和employeePassword字段,完成权限验证。


内容的提问来源于stack exchange,提问作者Nilton Schumacher F

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 15:55:55