Skip to content

CORS 跨域资源共享详解(含最佳实践)

适合读者 / 你将学到 / 阅读时长

  • 适合读者:需要调用跨域接口的前端/Node 工程师、网关/运维同学
  • 你将学到:同源与 CORS 的关系、简单请求与预检、凭据携带规则、缓存与 Vary、常见服务器配置
  • 阅读时长:15 分钟

核心概念

  • 同源策略(Same-Origin Policy):协议 + 域名 + 端口完全一致才是同源。
  • CORS:一种服务器声明“哪些源可访问我资源”的机制,基于 HTTP 头。

简单请求 vs 预检请求

  • 简单请求(Simple Request):直接发请求,浏览器仅在响应阶段检查 CORS。需满足:
    • 方法:GET/HEAD/POST
    • 头:仅限 Accept、Accept-Language、Content-Language、Content-Type(且值为 application/x-www-form-urlencoded、multipart/form-data 或 text/plain)
    • 无自定义头、无复杂 Content-Type
  • 预检请求(Preflight):其他情况,浏览器会先发 OPTIONS 请求询问服务端是否允许,服务端需返回允许的源/方法/头等,随后才发送真正请求。

关键响应头

  • Access-Control-Allow-Origin: 允许的来源,值可以是具体域名或 *
  • Access-Control-Allow-Methods: 允许的方法列表,如 GET,POST,PUT,DELETE
  • Access-Control-Allow-Headers: 允许的请求头(当请求包含自定义头时返回)。
  • Access-Control-Allow-Credentials: 是否允许携带凭据(Cookie、Authorization 等),值为 true/false
  • Access-Control-Expose-Headers: 暴露给浏览器 JS 可读的响应头。
  • Access-Control-Max-Age: 预检结果的缓存时间(秒)。

注意:当 Allow-Credentials: true 时,Allow-Origin 不能为 *,必须为明确的源。

  • 前端需在请求中显式开启:fetch(url, { credentials: 'include' })axios.defaults.withCredentials = true
  • 服务器需返回:Access-Control-Allow-Credentials: trueAccess-Control-Allow-Origin 为具体域名。
  • 跨站 Cookie 需要 SameSite=None; Secure,否则不会随跨站请求发送,也无法被设置。

缓存与 Vary

  • 使用 Access-Control-Max-Age 减少频繁预检,例如 600(10 分钟)。
  • 当你按“来源”动态返回 Allow-Origin 时,应加 Vary: Origin,以免 CDN/缓存污染其他来源。

常见服务端配置

Nginx(推荐在网关层统一处理)

nginx
location /api/ {
 if ($request_method = 'OPTIONS') {
  add_header 'Access-Control-Allow-Origin' $http_origin always;
  add_header 'Access-Control-Allow-Methods' 'GET,POST,PUT,DELETE,OPTIONS' always;
  add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With' always;
  add_header 'Access-Control-Allow-Credentials' 'true' always; # 如需携带凭据
  add_header 'Access-Control-Max-Age' 600 always;              # 预检缓存
  add_header 'Vary' 'Origin';
  return 204;
 }

 add_header 'Access-Control-Allow-Origin' $http_origin always;
 add_header 'Access-Control-Allow-Credentials' 'true' always;
 add_header 'Vary' 'Origin';

 proxy_pass http://backend;
}

提示:若不需要携带凭据,可将 Allow-Credentials 去掉并把 Allow-Origin 固定为 *

Node(Express + cors 中间件)

js
// Express + cors 中间件(CommonJS 示例)
const express = require('express');
const cors = require('cors');

const app = express();
app.use(
  cors({
    origin: [/^https?:\/\/localhost:\d+$/, 'https://www.cmwrun.com'],
    credentials: true, // 需要携带 Cookie/Authorization 时开启
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
    maxAge: 600,
  })
);

app.get('/api/data', (req, res) => {
  res.json({ ok: true });
});

app.listen(3000);

Koa(手写头部示例)

js
// Koa 简易示例(按需调整到应用入口)
const Koa = require('koa');
const app = new Koa();

app.use(async (ctx, next) => {
  const origin = ctx.request.headers.origin;
  const allowList = new Set(['https://www.cmwrun.com', 'http://localhost:5173']);
  if (origin && allowList.has(origin)) {
    ctx.set('Access-Control-Allow-Origin', origin);
    ctx.set('Access-Control-Allow-Credentials', 'true');
    ctx.set('Vary', 'Origin');
  }
  ctx.set('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
  ctx.set('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With');
  ctx.set('Access-Control-Max-Age', '600');
  if (ctx.method === 'OPTIONS') ctx.status = 204; else await next();
});

常见错误与排查

  • 返回了 Access-Control-Allow-Credentials: trueAllow-Origin: * → 违规则组合,浏览器直接拦截。
  • 忘记 Vary: Origin 导致 CDN 缓存污染,A 源缓存命中到 B 源请求。
  • 只配了 Allow-Headers,却在预检时漏了 Authorization 等自定义头。
  • 预检返回非 2xx/204 或者漏配 OPTIONS 路由(Node 常见)。
  • 跨站 Cookie 未设置 SameSite=None; Secure,导致怎么都带不上 Cookie。

快速自查清单

  • 设置 Allow-Origin 为明确域名或回显 $http_origin,并配 Vary: Origin
  • 需要凭据?→ 前端 withCredentials/credentials=include + 服务端 Allow-Credentials: true,且 Allow-Origin 不为 *
  • 确认 OPTIONS 预检能 204 返回,并包含允许的方法与头
  • 适度设置 Max-Age,减少预检次数

本地调试建议

bash
# 使用 curl 观察预检
curl -i -X OPTIONS \
  -H "Origin: http://localhost:5173" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Authorization, Content-Type" \
  https://api.example.com/resource

# 观察实际请求的 CORS 响应头
curl -i -H "Origin: http://localhost:5173" https://api.example.com/resource

参考:Fetch 标准(CORS 章节)、MDN CORS 指南、Nginx 文档、expressjs/cors

内容仅供学习参考,如有错误欢迎指正与 PR