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
相关产品推荐
相关产品推荐

