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

NestJS报错Cannot set headers after they are sent修复方案

问题描述

在NestJS项目中同时配置全局中间件与全局守卫时,若请求进入守卫逻辑后直接返回响应(未流转到控制器层),会出现两个异常:客户端收到404状态码,同时NestJS抛出错误:Cannot set headers after they are sent to the client。禁用该全局中间件后,所有功能可正常运行。

原有问题代码

全局中间件代码

@Injectable()
export class AuthMiddleware implements NestMiddleware {
  @Inject()
  private readonly usersService: UsersService;
  @Inject()
  private readonly tokenService: TokenService;

  async use(req: Request, res: Response, next: NextFunction): Promise<void> {
    if (!req.cookies.token) {
      req.user = {
        userId: null,
        personId: null,
        role: null,
      };

      next();
    }

    try {
      const result = this.tokenService.verifyToken(req.cookies.token);
      const user = await this.usersService.findOne(result.userId);

      if (!user) {
        req.user = {
          userId: null,
          personId: null,
          role: null,
        };

        next();
      }

      req.user = {
        userId: result.userId,
        personId: result.personId,
        role: result.role,
      };
    } catch {
      req.user = {
        userId: null,
        personId: null,
        role: null,
      };
    }

    next();
  }
}

全局守卫代码

@Injectable()
export class RoleGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const req: Request = await context.switchToHttp().getRequest();

    const requiredRole = this.reflector.get<Roles | null>(
      'requiredRole',
      context.getHandler(),
    );

    if (!requiredRole) {
      return true; // 逻辑在此处异常
    }

    if (
      requiredRole !== req.body.user.role &&
      req.body.user.role !== Roles.ADMIN
    ) {
      return false;
    }

    return true;
  }
}

main.ts 配置

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.use(cookieParser());
  app.use(urlencoded({ extended: true }));
  app.useGlobalPipes(
    new ValidationPipe({
      transform: true,
    }),
  );
  app.useGlobalGuards(new RoleGuard(new Reflector()));

  await app.listen(3000);
}
bootstrap();

app.module.ts 配置

@Module({
  imports: [
    ConfigModule.forRoot({
      envFilePath: '.env',
    }),
    TypeOrmModule.forRoot(getOrmConfig()),
    UserModule,
    EventModule,
  ],
  controllers: [],
  providers: [RoleGuard],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(AuthMiddleware).forRoutes({
      path: '*',
      method: RequestMethod.ALL,
    });
  }
}

问题根因

一共有3处代码错误共同导致该异常:

  • 中间件重复调用next():AuthMiddleware中存在多处next()调用,且分支逻辑没有中断执行,同一个请求会被多次放行到后续流程。第一次next()触发守卫、路由响应流程后,第二次next()会再次尝试触发后续逻辑,此时响应头已经发送给客户端,就会抛出Cannot set headers after they are sent to the client错误,同时请求匹配不到对应路由返回404。
  • 守卫读取用户信息的路径错误:中间件将解析后的用户信息挂载到req.user属性上,但守卫中错误地从req.body.user读取用户角色,导致权限判断逻辑取值为undefined,判断逻辑完全失效。
  • 全局守卫重复注册:既在main.ts中通过useGlobalGuards注册了全局守卫,又在AppModule的providers数组中声明了RoleGuard,会导致守卫逻辑被重复执行,放大上述异常的影响。

修复方案

按以下步骤修改代码即可解决问题:

  • 重构AuthMiddleware逻辑,保证每个请求分支仅调用一次next(),分支处理完成后及时中断后续代码执行,修复后的中间件代码如下:
@Injectable()
export class AuthMiddleware implements NestMiddleware {
  @Inject()
  private readonly usersService: UsersService;
  @Inject()
  private readonly tokenService: TokenService;

  async use(req: Request, res: Response, next: NextFunction): Promise<void> {
    // 无token分支:赋值后直接next,终止后续逻辑
    if (!req.cookies.token) {
      req.user = {
        userId: null,
        personId: null,
        role: null,
      };
      return next();
    }

    try {
      const result = this.tokenService.verifyToken(req.cookies.token);
      const user = await this.usersService.findOne(result.userId);

      // token对应用户不存在分支:赋值后直接next,终止后续逻辑
      if (!user) {
        req.user = {
          userId: null,
          personId: null,
          role: null,
        };
        return next();
      }

      // token有效、用户存在分支:赋值正常用户信息
      req.user = {
        userId: result.userId,
        personId: result.personId,
        role: result.role,
      };
    } catch {
      // token校验失败分支:赋值空用户信息
      req.user = {
        userId: null,
        personId: null,
        role: null,
      };
    }

    // 所有分支最终仅调用一次next
    next();
  }
}
  • 修正RoleGuard中用户信息的读取路径,从req.user上读取角色属性,修复后的守卫代码如下:
@Injectable()
export class RoleGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const req: Request = context.switchToHttp().getRequest();

    const requiredRole = this.reflector.get<Roles | null>(
      'requiredRole',
      context.getHandler(),
    );

    if (!requiredRole) {
      return true;
    }

    // 修正:从req.user读取角色,而非req.body.user
    if (
      requiredRole !== req.user.role &&
      req.user.role !== Roles.ADMIN
    ) {
      return false;
    }

    return true;
  }
}
  • 移除重复的守卫注册:如果保留main.ts中app.useGlobalGuards(new RoleGuard(new Reflector()))的注册方式,就删除AppModule的providers数组中的RoleGuard声明;如果要支持守卫的依赖注入,就删除main.ts中的手动注册,在AppModule的providers中通过Nest内置的APP_GUARD令牌注册:
import { APP_GUARD } from '@nestjs/core';

@Module({
  // 其他imports、controllers配置不变
  providers: [
    {
      provide: APP_GUARD,
      useClass: RoleGuard,
    }
  ],
})

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.31 06:09:10