Skip to content

HTML5 - Web CORS

跨域资源共享(CORS) 是网页浏览器实现的一种安全机制,它控制着来自一个源(域名、协议和端口)的网页如何请求和访问位于不同源服务器上的资源。

默认情况下,浏览器执行同源策略(Same-Origin Policy, SOP),该策略限制网页向提供该网页的域之外的其他域发送请求。这可以防止某个网站上的恶意脚本读取用户可能已登录的其他网站上的敏感数据。

CORS 提供了一种受控的方式,允许服务器放宽 SOP,并明确允许来自特定源(或所有源)的请求,从而支持合法的跨域交互,例如从托管在不同域的公共 API 获取数据。

CORS 通过在浏览器和服务器之间的通信中添加新的 HTTP 头来工作:

  1. 浏览器请求: 当 https://client.com 上的脚本尝试向 https://api.server.com 发起跨域请求时(例如,使用 fetch 或 XMLHttpRequest),浏览器会自动向 HTTP 请求添加一个 Origin 头:Origin: https://client.com。
  2. 服务器响应: https://api.server.com 上的服务器检查 Origin 头。如果服务器配置允许来自 https://client.com 的请求,则在其响应中包含特定的 CORS 头。最重要的头是 Access-Control-Allow-Origin。
  3. 浏览器检查: 浏览器接收到响应。它检查 Access-Control-Allow-Origin 头是否存在,并且其值是否与请求脚本的源(https://client.com)匹配或为通配符(*)。如果源被允许,浏览器就允许脚本访问响应数据。如果不允许,浏览器会阻止脚本访问响应,并在控制台中生成一个 CORS 错误。

简单请求(Simple Requests)与预检请求(Preflighted Requests)

Section titled “简单请求(Simple Requests)与预检请求(Preflighted Requests)”

CORS 区分“简单”请求和需要“预检”检查的请求:

如果请求满足以下所有条件,则被视为“简单”请求:

  • 方法是 GET、HEAD 或 POST。
  • 头信息只包含“简单”头,如 Accept、Accept-Language、Content-Language、Content-Type(具有特定值,如 application/x-www-form-urlencoded、multipart/form-data、text/plain)以及少数其他头。
  • 在任何 XMLHttpRequestUpload 对象上没有注册事件监听器。

对于简单请求,浏览器直接发送实际请求,并在响应中检查 Access-Control-Allow-Origin 头。

预检请求(Preflighted Requests,使用 OPTIONS)

Section titled “预检请求(Preflighted Requests,使用 OPTIONS)”

不符合“简单”标准的请求(例如,使用 PUT、DELETE、PATCH 等方法,或包含 Authorization 或 Content-Type: application/json 等自定义头)需要进行预检请求。

在发送实际请求之前,浏览器会自动向服务器 URL 发送一个 OPTIONS 请求。这个 OPTIONS 请求包含表明实际请求打算使用的 HTTP 方法(Access-Control-Request-Method)和任何自定义头(Access-Control-Request-Headers)的头信息。

服务器使用以下头信息响应 OPTIONS 请求:

  • Access-Control-Allow-Origin: 指定允许的源。
  • Access-Control-Allow-Methods: 列出实际请求允许的 HTTP 方法(例如,GET, POST, PUT, DELETE)。
  • Access-Control-Allow-Headers: 列出实际请求允许的自定义头。
  • Access-Control-Max-Age: 指定预检请求的结果可以缓存多长时间(秒)。

如果预检响应表明实际请求被允许(源、方法、头信息匹配),浏览器 then proceeds to send the actual request (e.g., the PUT request with Content-Type: application/json). 如果预检失败,则不会发送实际请求,并会发生 CORS 错误。

发起 CORS 请求(客户端示例,使用 Fetch)

Section titled “发起 CORS 请求(客户端示例,使用 Fetch)”

现代 JavaScript 通常使用 fetch API 来发起 HTTP 请求。浏览器会自动处理添加 Origin 头和执行预检检查。

const apiUrl = 'https://api.example.com/data'; // A cross-origin API endpoint
// const apiUrl = 'https://api.example.com/data'; // 一个跨域的 API 端点
async function fetchData() {
console.log(`Fetching data from ${apiUrl}...`);
// console.log(`正在从 ${apiUrl} 获取数据...`);
try {
const response = await fetch(apiUrl, {
method: 'GET', // Simple request (if no custom headers)
// method: 'GET', // 简单请求(如果没有自定义头)
// For preflighted requests, add headers etc:
// 对于预检请求,需要添加头等信息:
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// 'Authorization': 'Bearer your_token'
// },
// body: JSON.stringify({ key: 'value' })
});
// Check if the request was successful (status code 200-299)
// 检查请求是否成功(状态码 200-299)
if (!response.ok) {
// Check if the error might be CORS-related (opaque response)
// 检查错误是否可能与 CORS 有关(不透明响应)
if (response.type === 'opaque') {
console.error('Request failed. This might be a CORS issue or network error. Check browser console.');
// console.error('请求失败。这可能是 CORS 问题或网络错误。请检查浏览器控制台。');
} else {
// Server returned an error status code
// 服务器返回了错误的状态码
console.error(`Server error: ${response.status} ${response.statusText}`);
// console.error(`服务器错误: ${response.status} ${response.statusText}`);
}
// Throw an error to be caught by the catch block
// 抛出错误,以便被 catch 块捕获
throw new Error(`HTTP error! status: ${response.status}`);
// throw new Error(`HTTP 错误! 状态: ${response.status}`);
}
// If response is OK, parse the data (e.g., as JSON)
// 如果响应正常,解析数据(例如,作为 JSON)
const data = await response.json();
console.log('Data received successfully:', data);
// console.log('数据接收成功:', data);
// Process the data...
// 处理数据...
} catch (error) {
// Handle network errors or errors thrown above
// 处理网络错误或上面抛出的错误
console.error('Fetch error:', error);
// console.error('Fetch 错误:', error);
// Display error message to the user
// 向用户显示错误消息
}
}
fetchData();

重要提示: 如果服务器未发送正确的 CORS 头(Access-Control-Allow-Origin),fetch Promise 不会立即因为 CORS 错误而拒绝。相反,浏览器会阻止脚本访问响应,这通常导致一个不透明响应(response.type === 'opaque')或 catch 块中的通用网络错误。详细的 CORS 错误消息通常只出现在浏览器的开发者控制台中。

配置 CORS 是在服务器端完成的。具体实现取决于所使用的服务器端语言、框架或 Web 服务器软件(如 Apache、Nginx)。

通常,服务器配置涉及:

  • 检查接收到的请求的 Origin 头。
  • 在响应中添加 Access-Control-Allow-Origin 头,将其值设置为特定的允许源(例如,https://client.com)或 *(允许任何源 - 使用时需谨慎,特别是对于需要身份验证的 API)。
  • 对于预检(OPTIONS)请求,响应中包含 Access-Control-Allow-Methods、Access-Control-Allow-Headers,以及可选的 Access-Control-Max-Age。

许多 Web 框架提供中间件或插件来简化 CORS 配置。

如果您遇到 CORS 问题,请检查浏览器的开发者控制台,查找类似以下的消息:

  • No 'Access-Control-Allow-Origin' header is present on the requested resource. (最常见的错误 - 服务器不允许该源)。
  • The 'Access-Control-Allow-Origin' header has a value '...' that is not equal to the supplied origin. (服务器允许了不同的源)。
  • Request header field ... is not allowed by Access-Control-Allow-Headers in preflight response. (预检响应中不允许某个请求头)。
  • Method ... is not allowed by Access-Control-Allow-Methods in preflight response. (预检响应中不允许某个 HTTP 方法)。

调试 CORS 通常需要检查客户端请求的详细信息,以及(至关重要的是)服务器端的 CORS 配置和响应头。