浏览器同源策略的例外:CORS跨域资源共享详解
字数 2980 2025-12-07 10:15:39
浏览器同源策略的例外:CORS跨域资源共享详解
一、 描述
CORS 是一种基于 HTTP 头信息的机制,它允许运行在一个源(Origin)上的 Web 应用,访问来自另一个源(您已学习过基础 CORS 知识,本讲聚焦于更深层的例外、复杂场景和高级配置)的服务端资源。它是现代浏览器实现“同源策略”的最重要、最安全的例外。简单来说,CORS 是服务器和浏览器之间协商的一个标准,服务器通过特定的 HTTP 响应头来声明允许哪些“外源”访问自己的资源。
二、 解题过程(核心原理与进阶实践)
第1步:深入理解“同源”与“跨域”的判定
- 核心概念回顾:同源策略要求协议、域名、端口三者完全一致。这是浏览器的安全基石,防止恶意网站读取另一网站的数据。
- 进阶思考:浏览器判断跨域发生在“请求的响应返回时”,而非发送请求时。这意味着跨域请求可以发出,但浏览器在收到响应后,会检查响应头,如果不符合CORS规则,则阻止前端JavaScript代码读取此响应。
第2步:CORS请求的分类与处理流程
CORS将请求分为两类,处理机制不同:
-
简单请求
- 定义条件(需同时满足):
- 使用以下方法之一:
GET,HEAD,POST。 - 仅能使用以下安全的首部字段集合:
AcceptAccept-LanguageContent-LanguageContent-Type(且值仅限于application/x-www-form-urlencoded,multipart/form-data,text/plain三者之一)DPRDownlinkSave-DataViewport-WidthWidth
- 请求中的任意
XMLHttpRequest.upload对象没有注册任何事件监听器。 - 请求中没有使用
ReadableStream对象。
- 使用以下方法之一:
- 处理流程:
- 浏览器直接发出请求,并在请求头中自动添加
Origin字段(例如Origin: https://www.example.com)。 - 服务器收到请求后,检查
Origin。如果允许,则在响应头中包含Access-Control-Allow-Origin: <允许的源>(如Access-Control-Allow-Origin: https://www.example.com或通配符*)。 - 浏览器收到响应,检查
Access-Control-Allow-Origin的值是否包含当前页面的源。如果是,则允许前端JavaScript访问响应;否则,抛出错误。
- 浏览器直接发出请求,并在请求头中自动添加
- 定义条件(需同时满足):
-
预检请求
- 触发条件(满足任一即可):
- 使用了
PUT,DELETE,CONNECT,OPTIONS,TRACE,PATCH方法。 - 设置了
Content-Type为application/json等非简单值。 - 设置了非简单请求头部字段集合之外的自定义头(如
X-Custom-Header)。
- 使用了
- 处理流程:
- 第一步:预检请求。在实际请求之前,浏览器会使用
OPTIONS方法自动发起一个“预检请求”。- 预检请求的头部包含:
Origin: 请求源。Access-Control-Request-Method: 后续实际请求将使用的方法(如PUT)。Access-Control-Request-Headers: 后续实际请求将携带的自定义头部(如X-Custom-Header)。
- 预检请求的头部包含:
- 第二步:服务器响应预检。服务器必须响应此预检请求,并在响应头中声明允许的策略:
Access-Control-Allow-Origin: 允许的源。Access-Control-Allow-Methods: 允许的方法列表(如GET, POST, PUT, DELETE)。Access-Control-Allow-Headers: 允许的头部列表(如X-Custom-Header, Content-Type)。Access-Control-Max-Age: (可选)本次预检结果的有效期(秒),在此时间内,对同一请求路径的实际请求不再发送预检。
- 第三步:浏览器决策。浏览器检查预检响应头,如果所有条件都满足(
Origin、Method、Headers都在允许范围内),则继续发送实际请求。否则,阻止实际请求的发送。 - 第四步:实际请求。预检通过后,浏览器发送实际请求,流程同简单请求。
- 第一步:预检请求。在实际请求之前,浏览器会使用
- 触发条件(满足任一即可):
第3步:高级场景与关键响应头详解
- 携带凭据(Credentials)的请求:
- 场景:前端请求需要发送
Cookie或HTTP认证信息。 - 配置:
- 前端:在
XMLHttpRequest或Fetch API中设置withCredentials: true。 - 服务器:响应头中必须设置
Access-Control-Allow-Credentials: true。且Access-Control-Allow-Origin不能为通配符*,必须是具体的源。
- 前端:在
- 场景:前端请求需要发送
- 暴露自定义响应头:
- 场景:服务器在响应中设置了自定义头(如
X-Custom-Data),默认情况下,前端JS无法通过getResponseHeader(‘X-Custom-Data’)读取。 - 配置:服务器需在响应头中设置
Access-Control-Expose-Headers: X-Custom-Data, Another-Header,将允许读取的头暴露给前端。
- 场景:服务器在响应中设置了自定义头(如
- 非简单 Content-Type 的处理:
- 这是最常见的触发预检的场景。发送
application/json时,一定会有预检请求。
- 这是最常见的触发预检的场景。发送
第4步:CORS的安全考量与实践建议
- 避免滥用通配符
*:Access-Control-Allow-Origin: *虽然方便,但意味着任何网站都能访问你的资源。在需要携带凭据时,绝对禁止使用通配符。 - 精确控制允许的源、方法和头部:根据最小权限原则,尽量明确列出允许的源(可维护一个白名单)、方法和头部,而不是使用通配符。
- 预检请求缓存:合理设置
Access-Control-Max-Age可以减少不必要的预检请求,提升性能。 - CORS与JSONP的对比:CORS更强大、更安全,支持所有HTTP方法,且错误处理更完善。JSONP仅支持GET,存在安全风险(如服务器被攻破,返回恶意脚本)。
- 服务器端配置:通常在Web框架的中间件或Web服务器(如Nginx)中统一配置CORS响应头。
总结:CORS 是浏览器同源策略的一个可控例外,其本质是服务器通过一系列以 Access-Control- 开头的HTTP响应头,向浏览器声明自己的资源分享策略。浏览器则扮演“策略执行者”的角色,严格检查这些响应头,决定是否让前端代码访问跨域请求的响应结果。理解简单请求与预检请求的流程差异,以及凭据、自定义头等高级场景的配置,是安全、正确实现跨域通信的关键。