Skip to content

Node系列 · Express:cors 中间件

上一章讲了 CORS 原理——这一章用 cors 中间件把它从手动配置响应头简化成一行 app.use(cors())

一、安装与最简配置

bash
npm install cors
javascript
const cors = require('cors');

// 允许所有跨域(仅开发环境!)
app.use(cors());

这一行相当于手动加了:

javascript
app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS, PATCH, HEAD');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, ...');
  next();
});

app.options('*', cors());  // 自动处理预检

二、生产配置

2.1 指定允许的 Origin

javascript
const cors = require('cors');

const ALLOWED_ORIGINS = [
  'https://app.example.com',
  'https://admin.example.com',
];

app.use(cors({
  origin: (origin, callback) => {
    // origin 在同源请求时为 undefined(直接放行)
    if (!origin || ALLOWED_ORIGINS.includes(origin)) {
      return callback(null, true);
    }
    callback(new Error('CORS not allowed'));
  },
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  maxAge: 86400,                  // 预检缓存 24 小时
}));

2.2 关键 options

选项默认说明
origin*允许的源
methodsGET, HEAD, PUT, PATCH, POST, DELETE允许的方法
allowedHeadersOrigin, X-Requested-With, ...允许的请求头
exposedHeaders(空)浏览器可读取的响应头
credentialsfalse是否允许 Cookie
maxAge5(秒)预检缓存时间

2.3 origin 三种写法

javascript
// 1. 字符串(具体域名或 *)
cors({ origin: 'https://app.example.com' });
cors({ origin: '*' });

// 2. 数组
cors({ origin: ['https://a.com', 'https://b.com'] });

// 3. 函数(动态判断)
cors({
  origin: (origin, callback) => {
    if (WHITELIST.includes(origin)) {
      callback(null, true);     // 允许
    } else {
      callback(new Error('Not allowed'));
    }
  },
});
javascript
app.use(cors({
  origin: 'https://app.example.com',  // 不能是 *
  credentials: true,
}));

客户端配合:

javascript
fetch('https://api.example.com/users', {
  credentials: 'include',         // 携带 Cookie
});

四、只对部分路由生效

不必全局 app.use(cors())——可以只对 API 路由生效:

javascript
// 公共 API:允许所有跨域
app.use('/api/public', cors());

// 私有 API:只允许特定 Origin + 携带 Cookie
app.use('/api/private', cors({
  origin: 'https://app.example.com',
  credentials: true,
}));

// 不需要跨域的内部路由:不挂 cors
app.use('/api/internal', requireAuth);

五、自定义成功 / 失败响应

javascript
app.use(cors({
  origin: (origin, callback) => {
    if (!WHITELIST.includes(origin)) {
      return callback(new Error('CORS blocked'));
    }
    callback(null, true);
  },
}));

// CORS 失败的统一错误处理
app.use((err, req, res, next) => {
  if (err.message === 'CORS blocked') {
    return res.status(403).json({ error: 'CORS not allowed' });
  }
  next(err);
});

六、不同环境差异化配置

javascript
const isDev = process.env.NODE_ENV !== 'production';

app.use(cors(isDev
  ? {
      // 开发环境:宽松
      origin: true,                // 反射所有 Origin
      credentials: true,
    }
  : {
      // 生产环境:严格白名单
      origin: (origin, cb) => {
        if (PROD_ORIGINS.includes(origin)) {
          cb(null, true);
        } else {
          cb(new Error('CORS blocked'));
        }
      },
      credentials: true,
      maxAge: 86400,
    }
));

七、常见问题

7.1 cors() 不生效?

中间件顺序问题——cors 必须在路由前注册:

javascript
// ✅ 正确
app.use(cors());
app.use(express.json());
app.use('/api/users', userRoutes);

// ❌ 错误:路由先于 cors
app.use('/api/users', userRoutes);
app.use(cors());  // 太晚了

7.2 预检失败?

浏览器 OPTIONS 请求的响应没有 CORS 头:

bash
$ curl -X OPTIONS https://api.example.com/users \
  -H "Origin: https://app.example.com" \
  -H "Access-Control-Request-Method: POST"

# 看响应头里有没有:
# Access-Control-Allow-Origin
# Access-Control-Allow-Methods
# Access-Control-Allow-Headers

cors 中间件会自动处理 OPTIONS 预检响应——前提是配置正确。

检查三处:

位置必须
服务端credentials: true + 具体 Origin(不能 *
客户端credentials: 'include'
浏览器看到 Access-Control-Allow-Credentials: true 才带 Cookie

八、最佳实践

场景推荐
开发环境app.use(cors()) 一行搞定
生产环境动态白名单 + credentials: true + maxAge: 86400
多环境用环境变量控制 origin 列表
只对部分路由局部 app.use('/api/xxx', cors(...))
安全永远不要在生产用 origin: '*'
缓存多 Origin 配 Vary: Origin(cors 中间件自动加)

九、小结

  • cors 中间件把手动配响应头简化成 app.use(cors())
  • 生产环境用 origin: 函数 做白名单校验
  • 携带 Cookie 必须三件套:服务端 credentials: true + 具体 Origin + 客户端 credentials: 'include'
  • 中间件顺序:corsexpress.json → 业务路由
  • cors 自动处理 OPTIONS 预检,但配置错误仍会失败
  • 开发用宽松配置,生产用严格白名单