1. 简介
如今,我们访问的网页在展示数据时,常常需要向不同服务器发起请求。这种跨域通信机制被称为 跨域资源共享(CORS)。本文将深入讲解什么是 CORS、浏览器如何实现 CORS 策略,以及为什么需要 预检请求(Preflight Request)。
2. 同源策略(Same Origin Policy)是什么?
在介绍 CORS 之前,先来了解浏览器的一项基础安全机制:同源策略(SOP)。SOP 的作用是限制一个源(origin)下的脚本与其他源的资源进行交互。
在 SOP 中,只有当两个 URL 的 协议(scheme)、域名(domain) 和 端口(port) 完全一致时,才被视为同源。例如:
https://www.baeldung.com
✅ 和https://www.baeldung.com/about
✅ 是同源https://www.baeldung.com
❌ 和http://www.baeldung.com
❌ 不是同源(协议不同)
浏览器通过 SOP 限制脚本使用 XMLHttpRequest
或 Fetch API
等方式向其他源发起请求,从而防止跨站请求伪造等安全问题。
3. 什么是 CORS?
CORS 是一种机制,允许一个源的脚本向另一个源发起请求。它通过一组特定的 HTTP 头部,让服务器决定是否允许跨域请求。浏览器根据这些头部来决定是否将响应返回给前端脚本。
根据浏览器处理方式的不同,CORS 请求分为三类:
3.1. 简单请求(Simple Requests)
满足以下条件的请求被称为简单请求:
- 请求方法是
GET
、HEAD
或POST
- 请求头只能是自动添加的
User-Agent
,或以下白名单中的头部:Accept
Accept-Language
Content-Language
Content-Type
(仅限application/x-www-form-urlencoded
、multipart/form-data
、text/plain
)
- 不使用
ReadableStream
对象 - 没有为
XMLHttpRequest.upload
添加事件监听器
例如,以下代码发起一个简单请求:
const xhr = new XMLHttpRequest();
const url = 'https://www.api.com?q=test';
xhr.open('GET', url);
xhr.onreadystatechange = requestHandler;
xhr.send();
浏览器会自动在请求头中添加 Origin
,服务器则通过 Access-Control-Allow-Origin
响应头告知是否允许该来源访问资源。
✅ 示例流程如下图所示:
如果服务器未设置该头部或来源不匹配,浏览器将阻止脚本访问响应内容,并在控制台报错:
❌ 示例错误如下图所示:
3.2. 非简单请求(Non-Simple Requests)
不符合简单请求条件的请求被称为 非简单请求(Preflighted Request)。浏览器会在正式请求前发送一个 OPTIONS
类型的预检请求,询问服务器是否允许该请求。
预检请求包含以下关键头部:
Origin
:请求来源Access-Control-Request-Method
:请求方法Access-Control-Request-Headers
:自定义请求头
服务器响应时需返回以下头部:
Access-Control-Allow-Origin
:允许的来源Access-Control-Allow-Methods
:允许的方法Access-Control-Allow-Headers
:允许的请求头Access-Control-Max-Age
:预检结果缓存时间(秒)
举个例子,如果我们在请求中加入自定义头部:
const xhr = new XMLHttpRequest();
const url = 'https://www.api.com?q=test';
xhr.open('GET', url);
xhr.setRequestHeader('custom-header', 'test');
xhr.onreadystatechange = requestHandler;
xhr.send();
浏览器会先发送预检请求:
✅ 成功响应后,正式请求才会被发出:
如果服务器未返回正确响应头,浏览器将阻止请求,并在控制台报错:
❌ 示例错误如下图所示:
3.3. 带凭证的请求(Credentialed Requests)
CORS 默认不允许携带凭证(如 Cookie、Authorization 头、TLS 客户端证书)。若需要携带,必须在请求中设置 withCredentials = true
,并且服务器必须返回以下两个头部:
Access-Control-Allow-Origin
:不能是*
,必须指定具体来源Access-Control-Allow-Credentials: true
示例代码如下:
const xhr = new XMLHttpRequest();
const url = 'https://www.api.com?q=test';
xhr.open('GET', url);
xhr.withCredentials = true;
xhr.send();
如果服务器未正确配置,浏览器将阻止请求并报错:
❌ 示例错误如下图所示:
4. 为什么需要预检请求?
CORS 是在同源策略之后提出的,为了兼容旧系统并增强安全性,引入了预检请求机制。它的作用如下:
✅ 保护服务器安全:防止服务器因未知请求而产生副作用(如删除数据)
✅ 动态适应变化:服务器可以根据请求动态决定是否允许特定方法或头部
✅ 兼容性好:对于不支持 CORS 的老系统,浏览器默认按不允许跨域处理
5. 总结
CORS 是现代 Web 开发中解决跨域问题的核心机制。浏览器将跨域请求分为三类:
请求类型 | 是否需要预检 | 特点说明 |
---|---|---|
简单请求 | ❌ | 满足特定条件,直接发送请求 |
非简单请求 | ✅ | 需要预检,服务器确认后才发送正式请求 |
带凭证的请求 | 可能需要 ✅ | 必须服务器允许,且来源不能为 * |
理解这些机制有助于我们更好地处理跨域场景,避免踩坑。