浏览器同源策略的例外:CORS跨域资源共享详解
字数 2980 2025-12-07 10:15:39

浏览器同源策略的例外:CORS跨域资源共享详解


一、 描述
CORS 是一种基于 HTTP 头信息的机制,它允许运行在一个源(Origin)上的 Web 应用,访问来自另一个源(您已学习过基础 CORS 知识,本讲聚焦于更深层的例外、复杂场景和高级配置)的服务端资源。它是现代浏览器实现“同源策略”的最重要、最安全的例外。简单来说,CORS 是服务器和浏览器之间协商的一个标准,服务器通过特定的 HTTP 响应头来声明允许哪些“外源”访问自己的资源。

二、 解题过程(核心原理与进阶实践)

第1步:深入理解“同源”与“跨域”的判定

  • 核心概念回顾:同源策略要求协议、域名、端口三者完全一致。这是浏览器的安全基石,防止恶意网站读取另一网站的数据。
  • 进阶思考:浏览器判断跨域发生在“请求的响应返回时”,而非发送请求时。这意味着跨域请求可以发出,但浏览器在收到响应后,会检查响应头,如果不符合CORS规则,则阻止前端JavaScript代码读取此响应

第2步:CORS请求的分类与处理流程
CORS将请求分为两类,处理机制不同:

  1. 简单请求

    • 定义条件(需同时满足)
      1. 使用以下方法之一:GET, HEAD, POST
      2. 仅能使用以下安全的首部字段集合
        • Accept
        • Accept-Language
        • Content-Language
        • Content-Type(且值仅限于 application/x-www-form-urlencoded, multipart/form-data, text/plain 三者之一)
        • DPR
        • Downlink
        • Save-Data
        • Viewport-Width
        • Width
      3. 请求中的任意 XMLHttpRequest.upload 对象没有注册任何事件监听器。
      4. 请求中没有使用 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访问响应;否则,抛出错误。
  2. 预检请求

    • 触发条件(满足任一即可)
      1. 使用了 PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH 方法。
      2. 设置了 Content-Typeapplication/json 等非简单值。
      3. 设置了非简单请求头部字段集合之外的自定义头(如 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: (可选)本次预检结果的有效期(秒),在此时间内,对同一请求路径的实际请求不再发送预检。
      • 第三步:浏览器决策。浏览器检查预检响应头,如果所有条件都满足(OriginMethodHeaders都在允许范围内),则继续发送实际请求。否则,阻止实际请求的发送。
      • 第四步:实际请求。预检通过后,浏览器发送实际请求,流程同简单请求。

第3步:高级场景与关键响应头详解

  • 携带凭据(Credentials)的请求
    • 场景:前端请求需要发送 CookieHTTP认证 信息。
    • 配置
      1. 前端:在 XMLHttpRequestFetch API 中设置 withCredentials: true
      2. 服务器:响应头中必须设置 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响应头,向浏览器声明自己的资源分享策略。浏览器则扮演“策略执行者”的角色,严格检查这些响应头,决定是否让前端代码访问跨域请求的响应结果。理解简单请求与预检请求的流程差异,以及凭据、自定义头等高级场景的配置,是安全、正确实现跨域通信的关键。

浏览器同源策略的例外:CORS跨域资源共享详解 一、 描述 CORS 是一种基于 HTTP 头信息的机制,它允许运行在一个源(Origin)上的 Web 应用,访问来自另一个源(您已学习过基础 CORS 知识,本讲聚焦于更深层的例外、复杂场景和高级配置)的服务端资源。它是现代浏览器实现“同源策略”的最重要、最安全的例外。简单来说,CORS 是服务器和浏览器之间协商的一个标准,服务器通过特定的 HTTP 响应头来声明允许哪些“外源”访问自己的资源。 二、 解题过程(核心原理与进阶实践) 第1步:深入理解“同源”与“跨域”的判定 核心概念回顾 :同源策略要求协议、域名、端口三者完全一致。这是浏览器的安全基石,防止恶意网站读取另一网站的数据。 进阶思考 :浏览器判断跨域发生在“请求的响应返回时”,而非发送请求时。这意味着跨域请求 可以发出 ,但浏览器在收到响应后,会检查响应头,如果不符合CORS规则,则 阻止前端JavaScript代码读取此响应 。 第2步:CORS请求的分类与处理流程 CORS将请求分为两类,处理机制不同: 简单请求 定义条件(需同时满足) : 使用以下方法之一: GET , HEAD , POST 。 仅能使用以下安全的首部字段集合 : Accept Accept-Language Content-Language Content-Type (且值仅限于 application/x-www-form-urlencoded , multipart/form-data , text/plain 三者之一) DPR Downlink Save-Data Viewport-Width Width 请求中的任意 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响应头,向浏览器声明自己的资源分享策略。浏览器则扮演“策略执行者”的角色,严格检查这些响应头,决定是否让前端代码访问跨域请求的响应结果。理解简单请求与预检请求的流程差异,以及凭据、自定义头等高级场景的配置,是安全、正确实现跨域通信的关键。