5月28日 01:39

iframe 的同源策略是什么?跨域 iframe 如何通信?

同源策略是浏览器最核心的安全机制,它规定协议、域名、端口三者完全相同的两个页面才属于同源。iframe 作为一个独立的浏览上下文,其内容必须遵守同源策略——跨域 iframe 无法直接访问父页面的 DOM、Cookie 和 JavaScript 对象。

同源的判定规则

三个条件必须同时满足:协议相同(http/https)、域名相同、端口相同。任何一个不同即为跨域。

javascript
// 与 https://example.com/page.html 同源 https://example.com/other.html https://example.com/sub/page.html // 与 https://example.com/page.html 不同源 https://www.example.com/page.html // 子域名不同 http://example.com/page.html // 协议不同 https://example.com:8080/page.html // 端口不同

跨域 iframe 受到的限制

跨域 iframe 无法执行以下操作:

  • 访问 DOM:无法读写对方的 document 对象,访问 contentDocument 会抛出 SecurityError
  • 调用 JavaScript:无法调用对方 window 上的函数或访问变量
  • 读取存储数据:无法访问对方的 Cookie、localStorage、sessionStorage、IndexedDB
  • 获取网络请求:无法拦截或读取对方的 XMLHttpRequest/fetch 请求内容
javascript
// 尝试访问跨域 iframe 的 DOM const iframe = document.getElementById('myIframe'); try { const doc = iframe.contentDocument; // 抛出 SecurityError } catch (e) { console.error('跨域访问被拒绝'); }

跨域 iframe 通信方案

postMessage(推荐方案)

window.postMessage 是 W3C 标准的跨域通信 API,所有现代浏览器均支持。它允许两个不同源的窗口之间安全地传递消息。

父页面发送消息:

javascript
const iframe = document.getElementById('myIframe'); iframe.contentWindow.postMessage( { type: 'getData', payload: { key: 'value' } }, 'https://child-domain.com' // 必须指定目标源,不要用 '*' );

iframe 接收并响应:

javascript
window.addEventListener('message', (event) => { if (event.origin !== 'https://parent-domain.com') return; if (event.data.type === 'getData') { const result = processData(event.data.payload); window.parent.postMessage( { type: 'dataResponse', payload: result }, 'https://parent-domain.com' ); } });

使用 postMessage 的核心原则:发送时指定具体的 targetOrigin(不用 *),接收时严格校验 event.origin。

document.domain(已弃用)

该方案适用于同主域名下不同子域的通信,双方将 document.domain 设置为相同的主域名即可互相访问。

javascript
// 父页面 https://www.example.com document.domain = 'example.com'; // iframe https://sub.example.com document.domain = 'example.com';

注意:Chrome 115+ 已弃用 document.domain 的赋值操作,设置时会抛出异常。如需在子域间通信,应使用 postMessage 替代。如果你的项目仍需兼容旧方案,可通过设置 Origin-Agent-Cluster: ?0 响应头临时恢复,但这并非长久之计。

CORS(跨域资源共享)

CORS 解决的是跨域 HTTP 请求的问题,而非 iframe DOM 访问。当 iframe 内的页面需要请求父域的 API 时,服务器端需设置 CORS 响应头:

http
Access-Control-Allow-Origin: https://parent-domain.com Access-Control-Allow-Credentials: true

CORS 只允许 iframe 内部发跨域网络请求,不能突破 DOM 访问限制。

location.hash / window.name(老旧方案)

这两种方案是早期的跨域 iframe 通信方式,原理分别是通过 URL hash 片段和 window.name 属性传递数据,容量有限且实现复杂,已被 postMessage 完全取代,了解即可。

iframe 安全防护实践

sandbox 属性

sandbox 是限制 iframe 权限的核心属性,未设置时 iframe 拥有完整权限,设置后仅开放显式声明的权限:

html
<iframe src="https://external.com" sandbox="allow-scripts allow-same-origin" ></iframe>

常用 sandbox 值:

  • allow-scripts:允许执行 JavaScript
  • allow-same-origin:将 iframe 内容视为同源(谨慎使用)
  • allow-forms:允许提交表单
  • allow-popups:允许弹窗
  • 不设置任何值:最严格,禁止所有能力

关键提醒:同时设置 allow-scripts 和 allow-same-origin 等于放弃了沙箱保护,因为 iframe 内的脚本可以移除 sandbox 属性。对不可信内容,不要同时开启这两个值。

CSP 内容安全策略

通过 HTTP 响应头控制哪些来源的页面可以被嵌入:

http
Content-Security-Policy: frame-src 'self' https://trusted-domain.com;

也可以用 X-Frame-Options(旧方案)防止页面被 iframe 嵌入:

http
X-Frame-Options: DENY # 禁止任何嵌入 X-Frame-Options: SAMEORIGIN # 仅同源可嵌入

Cross-Origin 隔离策略

现代浏览器提供了更细粒度的跨域隔离头部:

  • Cross-Origin-Opener-Policy (COOP):控制跨域打开者关系,防止 window.opener 泄露
  • Cross-Origin-Embedder-Policy (COEP):控制页面可以加载哪些跨域资源
  • Cross-Origin-Resource-Policy (CORP):控制跨域资源是否可被其他源读取

三者配合使用可以实现完整的跨域隔离,启用后页面可访问 SharedArrayBuffer 等高精度 API。

面试追问

Q: 如何检测 iframe 是否同源? 尝试访问 iframe.contentDocument,如果抛出异常则为跨域。这是最简单可靠的方式。

Q: postMessage 传递的数据会被截获吗? postMessage 的消息对所有同源的监听器可见,但跨域页面只能收到发给自己 origin 的消息。关键是接收方必须校验 event.origin,否则同域内的恶意脚本可以伪造消息。

Q: sandbox 设置 allow-scripts allow-same-origin 有什么风险? iframe 内的脚本可以通过 frameElement.removeAttribute('sandbox') 移除沙箱限制,等于沙箱形同虚设。对不受信任的 iframe 不要同时启用这两个值。

Q: document.domain 为什么被弃用? 它放松了同源策略,导致同一主域名下所有子域共享同一安全边界,任何一个子域被攻破都会影响整个主域。现代浏览器通过 Origin-Keyed Agent Clusters 机制将其逐步淘汰。

标签:Iframe