NestJS CORS配置

学习目标

  • 理解CORS(跨域资源共享)的基本概念
  • 掌握NestJS中CORS的配置方法
  • 学习如何设置CORS的安全策略
  • 了解预检请求(OPTIONS)的处理机制
  • 掌握CORS配置的最佳实践

核心知识点

1. CORS简介

CORS(Cross-Origin Resource Sharing,跨域资源共享)是一种浏览器安全机制,它允许或限制从一个域向另一个域发起的HTTP请求。CORS的主要目的是保护用户数据的安全,防止恶意网站获取用户在其他网站上的敏感信息。

当浏览器发起跨域请求时,会执行以下步骤:

  1. 浏览器首先发送一个预检请求(OPTIONS请求)到目标服务器
  2. 服务器返回CORS头部信息,指示是否允许该跨域请求
  3. 浏览器根据服务器的响应决定是否发送实际的请求

2. NestJS中的CORS配置

在NestJS中,我们可以通过enableCors()方法来配置CORS:

// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // 启用CORS
  app.enableCors();
  
  await app.listen(3000);
}
bootstrap();

3. CORS配置选项

NestJS的CORS配置支持以下选项:

  • origin:指定允许的源,可以是字符串、字符串数组或函数
  • methods:指定允许的HTTP方法
  • allowedHeaders:指定允许的请求头
  • exposedHeaders:指定暴露给客户端的响应头
  • credentials:指定是否允许携带凭证(如cookie)
  • maxAge:指定预检请求的缓存时间(秒)
  • preflightContinue:指定是否继续处理预检请求
  • optionsSuccessStatus:指定预检请求成功的状态码

4. 详细配置示例

4.1 基本配置

// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // 启用CORS并配置选项
  app.enableCors({
    origin: 'http://localhost:3001', // 只允许来自localhost:3001的请求
    methods: ['GET', 'POST', 'PUT', 'DELETE'], // 允许的HTTP方法
    allowedHeaders: ['Content-Type', 'Authorization'], // 允许的请求头
    credentials: true, // 允许携带凭证
    maxAge: 3600, // 预检请求的缓存时间为1小时
  });
  
  await app.listen(3000);
}
bootstrap();

4.2 允许多个源

// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // 启用CORS并允许多个源
  app.enableCors({
    origin: [
      'http://localhost:3001',
      'http://localhost:3002',
      'https://example.com',
    ], // 允许的源列表
    methods: ['GET', 'POST', 'PUT', 'DELETE'],
    allowedHeaders: ['Content-Type', 'Authorization'],
    credentials: true,
  });
  
  await app.listen(3000);
}
bootstrap();

4.3 使用函数动态设置源

// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // 启用CORS并使用函数动态设置源
  app.enableCors({
    origin: (origin, callback) => {
      // 允许的源列表
      const allowedOrigins = [
        'http://localhost:3001',
        'http://localhost:3002',
        'https://example.com',
      ];
      
      // 检查请求源是否在允许列表中
      if (!origin || allowedOrigins.includes(origin)) {
        callback(null, true);
      } else {
        callback(new Error('Not allowed by CORS'));
      }
    },
    methods: ['GET', 'POST', 'PUT', 'DELETE'],
    allowedHeaders: ['Content-Type', 'Authorization'],
    credentials: true,
  });
  
  await app.listen(3000);
}
bootstrap();

5. 预检请求处理

预检请求(Preflight Request)是浏览器在发送实际跨域请求之前发送的OPTIONS请求,用于询问服务器是否允许该跨域请求。预检请求包含以下头部:

  • Origin:请求的源
  • Access-Control-Request-Method:实际请求将使用的HTTP方法
  • Access-Control-Request-Headers:实际请求将携带的自定义请求头

服务器对预检请求的响应应该包含以下CORS头部:

  • Access-Control-Allow-Origin:允许的源
  • Access-Control-Allow-Methods:允许的HTTP方法
  • Access-Control-Allow-Headers:允许的请求头
  • Access-Control-Allow-Credentials:是否允许携带凭证
  • Access-Control-Max-Age:预检请求的缓存时间

在NestJS中,当我们启用CORS后,框架会自动处理预检请求,我们不需要手动实现OPTIONS路由。

6. CORS与安全策略

正确配置CORS对于保护应用程序的安全至关重要。以下是一些CORS安全策略的最佳实践:

  1. 限制允许的源:只允许必要的源,避免使用通配符(*),特别是当credentials设置为true
  2. 限制允许的方法:只允许应用程序实际使用的HTTP方法
  3. 限制允许的请求头:只允许必要的请求头
  4. 合理设置缓存时间:设置适当的maxAge值,减少预检请求的数量
  5. 使用HTTPS:在生产环境中使用HTTPS,提高安全性

实用案例分析

案例1:前端应用与NestJS API的CORS配置

需求分析

我们有一个前端应用运行在http://localhost:3001,需要与运行在http://localhost:3000的NestJS API进行通信。前端应用需要发送带有认证令牌的请求,并且需要使用多种HTTP方法。

实现方案

  1. 配置NestJS的CORS
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // 配置CORS
  app.enableCors({
    origin: 'http://localhost:3001', // 只允许前端应用的源
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'], // 允许的HTTP方法
    allowedHeaders: [
      'Content-Type',
      'Authorization',
      'X-Requested-With',
      'Accept',
    ], // 允许的请求头
    exposedHeaders: ['Content-Length'], // 暴露给客户端的响应头
    credentials: true, // 允许携带凭证(如cookie)
    maxAge: 3600, // 预检请求的缓存时间为1小时
  });
  
  await app.listen(3000);
}
bootstrap();
  1. 前端应用发送请求
// 前端代码
// 发送GET请求
async function fetchData() {
  const response = await fetch('http://localhost:3000/api/data', {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer your-token-here',
    },
    credentials: 'include', // 包含凭证
  });
  const data = await response.json();
  console.log(data);
}

// 发送POST请求
async function postData() {
  const response = await fetch('http://localhost:3000/api/data', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer your-token-here',
    },
    credentials: 'include', // 包含凭证
    body: JSON.stringify({ name: 'Test', value: '123' }),
  });
  const data = await response.json();
  console.log(data);
}

案例2:生产环境的CORS配置

需求分析

我们需要在生产环境中配置CORS,允许来自特定域名的请求,并确保配置的安全性。

实现方案

  1. 使用环境变量管理CORS配置
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // 从环境变量获取允许的源
  const allowedOrigins = process.env.CORS_ALLOWED_ORIGINS
    ? process.env.CORS_ALLOWED_ORIGINS.split(',')
    : ['https://example.com'];
  
  // 配置CORS
  app.enableCors({
    origin: (origin, callback) => {
      // 允许的源列表
      if (!origin || allowedOrigins.includes(origin)) {
        callback(null, true);
      } else {
        callback(new Error('Not allowed by CORS'));
      }
    },
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
    allowedHeaders: [
      'Content-Type',
      'Authorization',
      'X-Requested-With',
      'Accept',
    ],
    credentials: true,
    maxAge: 86400, // 预检请求的缓存时间为24小时
  });
  
  await app.listen(3000);
}
bootstrap();
  1. 在部署配置中设置环境变量

Docker Compose配置

version: '3'
services:
  nestjs-api:
    image: nestjs-api:latest
    ports:
      - '3000:3000'
    environment:
      - NODE_ENV=production
      - CORS_ALLOWED_ORIGINS=https://example.com,https://www.example.com

Kubernetes配置

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nestjs-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nestjs-api
  template:
    metadata:
      labels:
        app: nestjs-api
    spec:
      containers:
      - name: nestjs-api
        image: nestjs-api:latest
        ports:
        - containerPort: 3000
        env:
        - name: NODE_ENV
          value: "production"
        - name: CORS_ALLOWED_ORIGINS
          value: "https://example.com,https://www.example.com"

案例3:使用中间件自定义CORS处理

需求分析

我们需要更灵活地控制CORS处理,例如根据不同的路由设置不同的CORS策略。

实现方案

  1. 创建CORS中间件
// src/common/middleware/cors.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class CorsMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    // 获取请求的源
    const origin = req.headers.origin as string;
    
    // 定义允许的源
    const allowedOrigins = [
      'http://localhost:3001',
      'https://example.com',
    ];
    
    // 检查请求源是否在允许列表中
    if (allowedOrigins.includes(origin)) {
      res.setHeader('Access-Control-Allow-Origin', origin);
    }
    
    // 设置其他CORS头部
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, Accept');
    res.setHeader('Access-Control-Allow-Credentials', 'true');
    res.setHeader('Access-Control-Max-Age', '86400');
    
    // 处理预检请求
    if (req.method === 'OPTIONS') {
      return res.status(204).send();
    }
    
    next();
  }
}
  1. 在应用模块中使用中间件
// src/app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { CorsMiddleware } from './common/middleware/cors.middleware';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersModule } from './users/users.module';

@Module({
  imports: [UsersModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    // 为所有路由应用CORS中间件
    consumer
      .apply(CorsMiddleware)
      .forRoutes('*');
    
    // 或者为特定路由应用CORS中间件
    // consumer
    //   .apply(CorsMiddleware)
    //   .forRoutes('api');
  }
}

常见问题与解决方案

1. CORS错误:No 'Access-Control-Allow-Origin' header

可能原因

  • CORS未启用
  • 允许的源配置错误
  • 服务器未返回正确的CORS头部

解决方案

  • 确保在NestJS应用中启用了CORS
  • 检查origin配置是否正确,确保包含了前端应用的源
  • 检查服务器是否返回了Access-Control-Allow-Origin头部

2. CORS错误:The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'

可能原因

  • credentials设置为true时,origin不能使用通配符(*

解决方案

  • 明确指定允许的源,而不是使用通配符
  • 或者将credentials设置为false

3. CORS错误:Method not allowed

可能原因

  • 请求使用的HTTP方法不在允许的方法列表中

解决方案

  • 在CORS配置中添加请求使用的HTTP方法

4. CORS错误:Request header field not allowed

可能原因

  • 请求使用的请求头不在允许的请求头列表中

解决方案

  • 在CORS配置中添加请求使用的请求头

5. 预检请求失败

可能原因

  • 服务器未正确处理OPTIONS请求
  • CORS配置错误

解决方案

  • 确保NestJS应用启用了CORS,框架会自动处理预检请求
  • 检查CORS配置是否正确

最佳实践

  1. 环境分离:根据不同的环境(开发、测试、生产)配置不同的CORS策略
  2. 最小权限原则:只允许必要的源、方法和请求头
  3. 使用环境变量:将CORS配置存储在环境变量中,便于管理和部署
  4. 合理设置缓存时间:设置适当的maxAge值,减少预检请求的数量
  5. 使用HTTPS:在生产环境中使用HTTPS,提高安全性
  6. 监控CORS错误:监控和记录CORS错误,及时发现和解决问题
  7. 文档化CORS配置:记录CORS配置的原因和变更历史

代码优化建议

  1. 创建CORS配置服务:将CORS配置逻辑封装到专门的服务中,提高代码可维护性
  2. 使用配置文件:将CORS配置存储在配置文件中,便于管理
  3. 实现动态CORS配置:根据请求的路由或其他条件动态调整CORS配置
  4. 添加CORS日志:为CORS请求添加日志,便于调试和监控
  5. 编写CORS测试:为CORS配置编写单元测试,确保配置的正确性

总结

CORS是现代Web应用开发中不可或缺的一部分,它允许前端应用与后端API进行安全的跨域通信。在NestJS中,配置CORS非常简单,我们可以通过enableCors()方法来启用和配置CORS。

通过本文的学习,你应该已经掌握了:

  • CORS的基本概念和工作原理
  • NestJS中CORS的配置方法
  • CORS配置选项的使用
  • 预检请求的处理机制
  • CORS安全策略的最佳实践
  • 常见CORS问题的解决方案

正确配置CORS对于保护应用程序的安全和确保前端与后端的正常通信至关重要。希望本文对你理解和配置NestJS应用的CORS有所帮助。

互动问答

  1. 什么是CORS?它的主要作用是什么?

  2. 如何在NestJS中启用CORS?

  3. credentials设置为true时,为什么不能使用通配符(*)作为origin

  4. 什么是预检请求(OPTIONS请求)?它的作用是什么?

  5. 在生产环境中,CORS配置的最佳实践有哪些?

« 上一篇 NestJS API文档 下一篇 » NestJS测试策略