CORS 简单请求完全指南
什么是简单请求
在 CORS(跨源资源共享)机制中,某些请求不会触发预检请求(Preflight Request),这类请求被称为简单请求。
设计动机
简单请求的设计基于这样一个事实:HTML 4.0 中的 <form> 元素早于跨站 XMLHttpRequest 和 fetch(),它可以向任何源提交简单请求。因此,任何编写服务器的开发者都必须已经在防护跨站请求伪造攻击(CSRF)。
在这个假设下,服务器不必通过响应预检请求来选择性接收任何看起来像表单提交的请求,因为 CSRF 的威胁并不比表单提交更严重。
简单请求的条件
若请求同时满足以下所有条件,则该请求可视为简单请求:
1. HTTP 方法限制
请求必须使用以下方法之一:
| 方法 | 说明 |
GET | 获取资源 |
HEAD | 获取资源元信息 |
POST | 提交数据 |
2. 请求头限制
除了被用户代理自动设置的标头字段(如 Connection、User-Agent),只允许手动设置以下 CORS 安全标头:
AcceptAccept-LanguageContent-LanguageContent-Type(需要注意额外限制)Range(仅允许简单的范围值,如bytes=256-或bytes=127-255)
3. Content-Type 限制
Content-Type 标头的值仅限于以下三者之一:
text/plain
multipart/form-data
application/x-www-form-urlencodedContent-Type 是 application/json、text/xml 等其他类型,将触发预检请求!4. XMLHttpRequest 特殊要求
如果请求是使用 XMLHttpRequest 对象发出的:
- 在
XMLHttpRequest.upload对象属性上没有注册任何事件监听器 - 即没有调用
xhr.upload.addEventListener()来监听上传请求
5. 不使用 ReadableStream
请求中没有使用 ReadableStream 对象。
简单请求的工作流程
请求示例
请求报文
Origin 字段表明该请求来源于 https://foo.example。这个标头在所有访问控制请求中总是被发送。响应报文
Access-Control-Allow-Origin 响应头
服务器通过 Access-Control-Allow-Origin 标头来控制跨源访问:
允许所有源
Access-Control-Allow-Origin: *表示该资源可以被任意外源访问。
限制特定源
Access-Control-Allow-Origin: https://foo.example限制只有 https://foo.example 域可以通过跨源访问该资源。
Access-Control-Allow-Origin 的值,而不能使用通配符 *。动态源需要 Vary 头
如果服务端指定了具体的单个源(可能会根据请求的来源动态改变),则响应标头中的 Vary 字段必须包含 Origin:
Access-Control-Allow-Origin: https://mozilla.org
Vary: Origin这将告诉客户端:服务器对不同的 Origin 返回不同的内容。
简单请求 vs 预检请求
| 特性 | 简单请求 | 预检请求 |
| 是否发送 OPTIONS | ❌ 否 | ✅ 是 |
| HTTP 方法 | GET、HEAD、POST | PUT、DELETE、PATCH 等 |
| Content-Type | text/plain、multipart/form-data、application/x-www-form-urlencoded | application/json、text/xml 等 |
| 自定义请求头 | ❌ 不允许 | ✅ 允许 |
| 请求次数 | 1 次 | 2 次(OPTIONS + 实际请求) |
浏览器兼容性说明
WebKit Nightly 和 Safari Technology Preview 为 Accept、Accept-Language 和 Content-Language 标头字段的值添加了额外的限制。如果这些标头字段的值是「非标准」的,WebKit/Safari 就不会将这些请求视为简单请求。
其他浏览器并不支持这些额外的限制,因为它们不属于规范的一部分。
最佳实践
1. 优先使用简单请求
当可能时,设计 API 以支持简单请求,可以减少一次网络往返: