什么是同源策略

同源策略(Same-Origin Policy,SOP) 是浏览器最核心、最基本的安全机制之一。它的核心目的是隔离来自不同“源”的文档或脚本,防止恶意网站窃取数据或破坏用户其他网站的会话。可以说,没有同源策略,Web 的安全性将荡然无存。

核心概念:

  1. 什么是“源”(Origin)?

    • 一个“源”由 三个要素 唯一确定:
      • 协议(Protocol):例如 http://, https://, ftp://, ws:// (WebSocket), wss:// 等。
      • 主机名(Hostname):例如 www.example.com, subdomain.example.com, localhost, 127.0.0.1
      • 端口(Port):例如 80 (HTTP默认), 443 (HTTPS默认), 8080, 3000 等。如果端口未明确指定,则使用该协议的默认端口。
    • 判断两个 URL 是否同源: 只有当两个 URL 的协议、主机名、端口三者完全一致时,它们才被认为是同源(Same Origin)。只要其中任何一个不同,就属于不同源(Cross-Origin)
  2. 同源策略的作用(限制了什么)?
    同源策略主要限制来自不同源的文档或脚本如何与当前源的资源进行交互。具体限制体现在以下几个方面:

    • DOM 访问:
      • 脚本(通常是 JavaScript)无法读取或操作来自不同源的页面的 DOM(Document Object Model)。
      • 例如: https://evil.com 页面上的脚本不能读取 https://bank.com 页面(如果嵌在 iframe 中)中的输入框内容、按钮状态或修改其 DOM 结构。这防止了恶意网站窃取用户在其他网站上的敏感信息。
    • 网络请求(AJAX / Fetch API):
      • 默认情况下,使用 XMLHttpRequestFetch API 发起的异步 HTTP 请求只能发送给同源的 URL。
      • 例如: https://app.example.com 页面上的脚本不能直接通过 AJAX 向 https://api.anothersite.com 发送请求并读取响应数据。这防止了恶意脚本窃取用户在另一个网站上的数据(前提是用户已登录该网站,且该网站存在漏洞)。
    • Cookie、LocalStorage、IndexedDB 等存储:
      • 浏览器存储的数据(如 Cookie、LocalStorage、SessionStorage、IndexedDB)是按进行隔离的。
      • 来自 https://siteA.com 的页面只能访问它自己源(https://siteA.com)设置的存储数据,无法访问 https://siteB.com 设置的存储数据。这保证了用户数据的隐私和安全。
    • 其他资源(如图片、字体、脚本、CSS):
      • 通常,嵌入跨源资源(如 <img src="...">, <script src="...">, <link rel="stylesheet" href="...">)是允许的。浏览器会加载这些资源。
      • 但是: 嵌入资源的页面不能直接读取这些跨源资源的内容(除非资源服务器明确允许,例如通过 CORS)。例如,你可以用 <img> 加载跨源图片显示在页面上,但你不能用 JavaScript 读取该图片的像素数据(除非该图片服务器设置了允许跨域访问的 CORS 头)。

为什么需要同源策略?

想象一下没有同源策略的世界:

  1. 你登录了网上银行 https://bank.com
  2. 然后你打开了另一个标签页访问了一个恶意网站 https://evil.com
  3. evil.com 的页面可以在后台悄悄加载一个 bank.com 的 iframe(假设银行会话 Cookie 有效)。
  4. evil.com 的脚本可以自由读取 bank.com iframe 里的 DOM,获取你的账户余额、交易记录,甚至操作 DOM 发起转账请求。
  5. 或者,evil.com 的脚本可以直接向 bank.com 的 API 发起 AJAX 请求,窃取你的数据或执行操作。

同源策略就是浏览器建立的“隔离墙”,阻止 evil.combank.com 进行上述操作,保护你的隐私和安全。

同源 vs 不同源示例:

当前页面 URL 目标 URL 是否同源 原因
https://www.example.com/page1 https://www.example.com/page2 协议、主机、端口(隐式443)完全相同
https://www.example.com/page1 https://www.example.com:8080/page2 端口不同 (443 vs 8080)
https://www.example.com/page1 http://www.example.com/page2 协议不同 (HTTPS vs HTTP)
https://www.example.com/page1 https://api.example.com/data 主机名不同 (www vs api)
https://www.example.com/page1 https://example.com/page2 主机名不同 (www.example.com vs example.com - 子域 vs 根域)
https://www.example.com/page1 https://www.anothersite.com/page2 主机名完全不同

如何安全地打破同源策略?

完全禁止跨源交互是不现实的,现代 Web 应用需要合法地整合不同源的资源(如调用第三方 API、使用 CDN 资源、嵌入地图/视频等)。因此,浏览器提供了一些在受控和安全前提下允许跨源交互的机制:

  1. CORS (跨源资源共享): 最重要、最标准的方式。

    • 当浏览器检测到一个跨源请求(如 AJAX/Fetch 请求)时,它会自动在请求头中添加一个 Origin 头,标明请求来自哪个源(如 Origin: https://www.mysite.com)。
    • 目标服务器需要检查这个 Origin。如果服务器允许该源访问其资源,它必须在响应中包含特定的 CORS 响应头,最重要的是:
      • Access-Control-Allow-Origin: https://www.mysite.com (或 * 表示允许任何源,但需谨慎使用)。
      • 对于需要携带凭据(如 Cookie、HTTP 认证)的请求或非简单请求(如 PUT、DELETE、带自定义头的请求),还需要其他 CORS 头(如 Access-Control-Allow-Credentials: true, Access-Control-Allow-Methods, Access-Control-Allow-Headers)以及可能的 预检请求(Preflight Request)
    • 浏览器收到响应后,会检查这些 CORS 头。只有当响应头明确允许当前页面的源时,浏览器才会将响应数据交给页面脚本;否则,浏览器会阻止脚本访问响应数据(尽管请求可能已经发送到服务器并被执行了,这是需要注意的安全点)。
    • 简单请求 vs 预检请求:
      • 简单请求: 使用特定方法(GET, POST, HEAD)和特定头(如 Accept, Accept-Language, Content-Language, Content-Type 仅限于 application/x-www-form-urlencoded, multipart/form-data, text/plain)的请求,浏览器直接发送,并在响应中检查 Access-Control-Allow-Origin
      • 预检请求: 对于不满足简单请求条件的请求(如 PUT, DELETE, Content-Type: application/json, 带自定义头),浏览器会自动发送一个 OPTIONS 方法的预检请求到目标服务器。这个请求携带 Origin, Access-Control-Request-Method, Access-Control-Request-Headers 等信息。服务器必须响应明确允许这些方法、头、源后,浏览器才会发送实际的请求。
  2. JSONP (JSON with Padding): 一种历史遗留方法(有安全风险,不推荐新项目使用)。

    • 利用 <script> 标签不受同源策略限制加载跨源 JS 文件的特性。
    • 客户端定义一个回调函数(如 handleResponse)。
    • 创建一个 <script> 标签,其 src 指向目标 API 的 URL,并附加一个查询参数指定回调函数名(如 ?callback=handleResponse)。
    • 服务器收到请求后,将数据包装在指定的回调函数调用中(如 handleResponse({ "data": ... });)返回。
    • 浏览器加载这个脚本并执行,相当于调用了客户端定义的回调函数,从而获取到数据。
    • 缺点: 只支持 GET 请求;难以处理错误;存在严重安全风险(如果服务器被攻陷返回恶意脚本);无法使用现代 Fetch API 的优点。
  3. 代理服务器:

    • 同源的服务器上设置一个代理。
    • 客户端向自己的同源服务器(如 /api/proxy)发送请求。
    • 该服务器作为中间人,将请求转发给真正的目标服务器(跨源服务器)。
    • 目标服务器响应给代理服务器。
    • 代理服务器再将响应原样(或处理后)返回给客户端。
    • 对浏览器来说,请求和响应都是在同源下完成的,绕开了同源策略。代理服务器需要处理身份验证、请求转发、响应处理等逻辑。
  4. postMessage API:

    • 允许来自不同源的 Window 对象(如 iframe, 新窗口, 标签页)之间进行安全的、受控的通信。
    • 发送方使用 targetWindow.postMessage(message, targetOrigin) 发送消息。
    • 接收方通过监听 message 事件来接收消息。在事件处理程序中,必须严格检查 event.origin 属性,只处理来自预期源的消息,以防止恶意消息。
    • 常用于不同源 iframe 之间的通信或与弹出窗口的通信。
  5. CORS 代理(开发调试用): 一些公开的 CORS 代理服务(如 cors-anywhere)可用于临时解决开发环境中的跨域问题,绝对不要在生产环境中使用

总结:

  • 同源策略是浏览器强制实施的、基于协议+主机名+端口三要素的安全沙箱机制。
  • 它的核心目的是阻止恶意网站读取或篡改来自其他源的数据,保护用户隐私和安全。
  • 它主要限制:DOM 访问、跨源网络请求(AJAX/Fetch)、跨源读取存储数据
  • CORS (跨源资源共享) 是现代 Web 上安全、标准地实现合法跨源通信的机制,需要服务器和客户端共同配合实现。
  • 其他方法如 JSONP(不推荐)、代理服务器、postMessage 等也可在特定场景下用于跨源交互,但各有优缺点和使用范围。

理解同源策略是理解 Web 安全(如 CSRF、XSS)和现代 Web 开发(API 调用、微前端)的基础。

posted @ 2025-07-03 17:22  bigger_apple  阅读(140)  评论(0)    收藏  举报