CORS 跨域原理

浏览器跨域资源共享的工作机制

问题

解释 CORS(Cross-Origin Resource Sharing)跨域的工作原理。

解答

什么是跨域

当协议、域名、端口三者任一不同时,就产生跨域。浏览器的同源策略会阻止跨域请求。

http://example.com/page
https://example.com/page    // 协议不同
http://api.example.com/page // 域名不同
http://example.com:8080/page // 端口不同

CORS 工作流程

CORS 通过 HTTP 头来告诉浏览器允许跨域访问。分为简单请求和预检请求两种情况。

简单请求

满足以下条件的是简单请求:

  • 方法:GET、HEAD、POST
  • 请求头仅包含:Accept、Accept-Language、Content-Language、Content-Type(仅限 text/plain、multipart/form-data、application/x-www-form-urlencoded)
// 前端发起简单请求
fetch('https://api.example.com/data', {
  method: 'GET',
  credentials: 'include' // 携带 cookie
})
// 请求头
GET /data HTTP/1.1
Origin: https://example.com

// 响应头
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Credentials: true

预检请求(Preflight)

不满足简单请求条件时,浏览器先发送 OPTIONS 请求进行预检。

// 前端发起非简单请求
fetch('https://api.example.com/data', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'X-Custom-Header': 'value'
  },
  body: JSON.stringify({ name: 'test' })
})
// 1. 预检请求
OPTIONS /data HTTP/1.1
Origin: https://example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, X-Custom-Header

// 2. 预检响应
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, X-Custom-Header
Access-Control-Max-Age: 86400  // 预检结果缓存时间(秒)

// 3. 预检通过后,发送实际请求
PUT /data HTTP/1.1
Origin: https://example.com
Content-Type: application/json

服务端配置示例

// Node.js Express
const express = require('express')
const app = express()

app.use((req, res, next) => {
  // 允许的源,* 表示所有(不能与 credentials 同时使用)
  res.setHeader('Access-Control-Allow-Origin', 'https://example.com')
  
  // 允许的方法
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
  
  // 允许的请求头
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
  
  // 允许携带凭证(cookie)
  res.setHeader('Access-Control-Allow-Credentials', 'true')
  
  // 预检请求缓存时间
  res.setHeader('Access-Control-Max-Age', '86400')
  
  // 处理预检请求
  if (req.method === 'OPTIONS') {
    return res.sendStatus(204)
  }
  
  next()
})
# Nginx 配置
location /api {
    add_header Access-Control-Allow-Origin https://example.com;
    add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE';
    add_header Access-Control-Allow-Headers 'Content-Type, Authorization';
    add_header Access-Control-Allow-Credentials true;
    
    if ($request_method = OPTIONS) {
        return 204;
    }
}

CORS 响应头说明

响应头说明
Access-Control-Allow-Origin允许访问的源
Access-Control-Allow-Methods允许的 HTTP 方法
Access-Control-Allow-Headers允许的请求头
Access-Control-Allow-Credentials是否允许携带凭证
Access-Control-Max-Age预检结果缓存时间
Access-Control-Expose-Headers允许前端访问的响应头

关键点

  • 同源策略:协议、域名、端口必须完全相同
  • 简单请求直接发送,非简单请求先发 OPTIONS 预检
  • CORS 是服务端通过响应头控制的,前端无法绕过
  • Access-Control-Allow-Origin: * 不能与 credentials: true 同时使用
  • 预检结果可通过 Access-Control-Max-Age 缓存,减少 OPTIONS 请求