Skip to content
On this page

CORS 策略

跨源资源共享(Cross-Origin Resource Sharing,CORS)是一种机制,它使用额外的 HTTP 头部来告诉浏览器,允许一个域上的 Web 应用程序访问另一个域上的资源。

CORS 基础概念

什么是同源策略

同源策略是浏览器的一种安全机制,它限制一个源(协议 + 域名 + 端口)的文档或脚本与另一个源的资源进行交互。

同源示例:

  • https://example.comhttps://example.com/page - 同源
  • https://example.com:8080https://example.com - 不同源(端口不同)

什么时候会发生跨源请求

  • https://site-a.comhttps://site-b.com 发起 AJAX 请求
  • https://example.com 加载来自 https://cdn.example.com 的资源
  • 嵌入不同源的 iframe

CORS 请求类型

简单请求

满足以下条件的请求被视为简单请求:

  1. 使用以下方法之一:

    • GET
    • POST
    • HEAD
  2. HTTP 头部仅包含:

    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type(值为 application/x-www-form-urlencoded、multipart/form-data 或 text/plain)

预检请求

对于不满足简单请求条件的请求,浏览器会先发送预检请求(OPTIONS 请求):

javascript
// 这样的请求会触发预检
fetch('https://api.example.com/data', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Custom-Header': 'value'
  },
  body: JSON.stringify({data: 'example'})
});

预检请求的示例:

OPTIONS /data HTTP/1.1
Host: api.example.com
Origin: https://example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-Custom-Header

带凭证的请求

使用 credentials: 'include' 时的特殊处理:

javascript
fetch('https://api.example.com/data', {
  credentials: 'include',  // 包含 cookies
  headers: {
    'Content-Type': 'application/json'
  }
});

CORS 响应头部

Access-Control-Allow-Origin

指定哪些源可以访问资源:

Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Origin: *

注意: 当使用 Access-Control-Allow-Credentials: true 时,Access-Control-Allow-Origin 不能设为 *

Access-Control-Allow-Methods

指定允许的 HTTP 方法:

Access-Control-Allow-Methods: GET, POST, PUT, DELETE

Access-Control-Allow-Headers

指定允许的请求头部:

Access-Control-Allow-Headers: X-Custom-Header, Content-Type

Access-Control-Allow-Credentials

指定是否允许发送凭据(cookies):

Access-Control-Allow-Credentials: true

Access-Control-Max-Age

指定预检请求的缓存时间(秒):

Access-Control-Max-Age: 86400  // 24小时

Access-Control-Expose-Headers

指定哪些头部可以被响应访问:

Access-Control-Expose-Headers: X-Total-Count, X-My-Custom-Header

服务器端实现

Node.js (Express)

javascript
// 手动设置 CORS 头部
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', 'https://example.com');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
  res.header('Access-Control-Allow-Credentials', true);
  
  if (req.method === 'OPTIONS') {
    res.sendStatus(200);
  } else {
    next();
  }
});

// 或使用 cors 中间件
const cors = require('cors');

const corsOptions = {
  origin: ['https://example.com', 'https://app.example.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true
};

app.use(cors(corsOptions));

Apache 配置

.htaccess 或虚拟主机配置中:

apache
# 允许特定源
Header set Access-Control-Allow-Origin "https://example.com"
Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Header set Access-Control-Allow-Headers "Content-Type, Authorization"

# 或允许所有源(不推荐用于生产环境)
Header set Access-Control-Allow-Origin "*"

Nginx 配置

nginx
location /api/ {
    add_header 'Access-Control-Allow-Origin' 'https://example.com';
    add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
    add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
    add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
    
    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Max-Age' 1728000;
        add_header 'Content-Type' 'text/plain; charset=utf-8';
        add_header 'Content-Length' 0;
        return 204;
    }
    
    proxy_pass http://backend;
}

安全考虑

避免通配符

对于需要凭据的请求,不要使用通配符:

javascript
// 不安全 - 使用通配符和凭据
app.use((req, res) => {
  res.header('Access-Control-Allow-Origin', '*');  // 不安全!
  res.header('Access-Control-Allow-Credentials', true);  // 与上面冲突
});

// 安全 - 指定特定源
app.use((req, res) => {
  const origin = req.headers.origin;
  if (['https://trusted-site.com', 'https://another-trusted.com'].includes(origin)) {
    res.header('Access-Control-Allow-Origin', origin);
  }
  res.header('Access-Control-Allow-Credentials', true);
});

验证请求源

始终验证请求的来源:

javascript
function validateOrigin(req, res, next) {
  const allowedOrigins = [
    'https://example.com',
    'https://app.example.com',
    'https://staging.example.com'
  ];
  
  const origin = req.headers.origin;
  
  if (allowedOrigins.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
  } else {
    res.status(403).send('Forbidden');
    return;
  }
  
  next();
}

限制暴露的头部

只暴露必要的响应头部:

javascript
// 不安全 - 暴露过多头部
res.header('Access-Control-Expose-Headers', '*');

// 安全 - 只暴露必要的头部
res.header('Access-Control-Expose-Headers', 'X-Total-Count, X-RateLimit-Remaining');

常见错误和解决方案

错误:CORS header 'Access-Control-Allow-Origin' does not match

原因: 同一请求返回了多个不同的 Origin 值

解决方案:

javascript
// 错误的方式
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Origin', 'https://example.com');  // 冲突!

// 正确的方式
const allowedOrigins = ['https://example.com', 'https://app.example.com'];
const origin = req.headers.origin;

if (allowedOrigins.includes(origin)) {
  res.header('Access-Control-Allow-Origin', origin);
}

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

解决方案:

javascript
// 不要使用通配符,指定具体源
res.header('Access-Control-Allow-Origin', 'https://example.com');
res.header('Access-Control-Allow-Credentials', true);

错误:Request header field X-Requested-With is not allowed by Access-Control-Allow-Headers

解决方案:

javascript
// 在响应中包含请求的头部
const requestedHeaders = req.headers['access-control-request-headers'];
res.header('Access-Control-Allow-Headers', requestedHeaders || 'Content-Type, Authorization');

最佳实践

  1. 明确指定允许的源 - 避免使用通配符,除非绝对必要
  2. 限制允许的方法 - 只允许必要的 HTTP 方法
  3. 验证请求头 - 只允许必要的请求头部
  4. 设置适当的缓存时间 - 避免不必要的预检请求
  5. 记录和监控 - 记录 CORS 相关的错误和异常
  6. 定期审查 - 定期检查 CORS 策略的有效性

调试 CORS 问题

浏览器开发者工具

在 Network 标签中查看请求和响应头部:

Request Headers:
- Origin: https://example.com

Response Headers:
- Access-Control-Allow-Origin: https://example.com

使用在线工具

  • CORS Test Tool
  • Postman(支持 CORS 模拟)

临时解决方案(仅用于开发)

javascript
// 开发环境的宽松 CORS 设置
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.header('Access-Control-Allow-Headers', '*');
  
  if (req.method === 'OPTIONS') {
    res.sendStatus(200);
  } else {
    next();
  }
});

注意: 上面的配置仅适用于开发环境,绝不能用于生产环境!

总结

CORS 是 Web 安全的重要组成部分,正确配置 CORS 策略可以保护应用程序免受跨站请求攻击,同时允许合法的跨域通信。关键是要平衡功能需求和安全要求,避免过度宽松的配置。