跳到主要内容

跨窗口通信

同源策略如何限制窗口之间的访问?​

  • 动机: john-smith.com 的脚本不能读 gmail.com 的邮件, 否则用户信息被窃;
  • 同源定义: 协议、域名、端口三者全部相同;
URL与 http://site.com/ 同源?
http://site.com/是;
http://site.com/my/page.html是 (路径不影响);
http://www.site.com否 (www. 算另一个域名);
http://site.org否 (域名不同);
https://site.com否 (协议不同);
http://site.com:8080否 (端口不同);
  • 同源: 对另一个窗口有完全访问权, 变量、document 都能读写;
  • 异源: 读不到对方任何内容; 唯一例外是 location — 可以写 (等于把对方跳转走), 不能读 (否则泄露用户当前访问的地址);
  • 前提: 先要有对方窗口的引用, 弹窗靠 window.open / window.opener, iframe 靠 iframe.contentWindow / window.frames, 见 弹窗与 iframe;
  • 结论: 异源时只剩两件事可做 — 写 location 和 postMessage;
  • 网络侧: 跨源 fetch 的判定与服务端放行头见 跨源请求与 CORS;

postMessage 在跨窗口场景怎么用?​

  • 定位: 绕过同源策略的唯一正式通道 — 双方都显式调用对应函数才通信, 因此对用户安全;
  • 取值: targetWin.postMessage(data, targetOrigin), 目标窗口引用来自 window.opener、window.frames[name] 或 iframe.contentWindow;
  • targetOrigin 的意义: 异源时发送方读不到目标窗口的 location, 无法确认它现在打开的是哪个站点 (用户可能已经导航走); 写上期望的源, 消息就只在目标仍在正确站点时送达, 传敏感数据时很重要;
// 目标必须仍是 http://example.com, 否则消息被丢弃
window.frames.example.postMessage("message", "http://example.com");
window.frames.example.postMessage("message", "*"); // 不做站点校验
  • data: 任意对象, 按结构化序列化克隆; IE 只支持字符串, 复杂对象需先 JSON.stringify;
  • 接收: 目标窗口注册 message 事件, 教程指出简写 window.onmessage 不生效, 要用 addEventListener;
window.addEventListener("message", (event) => {
if (event.origin !== "http://javascript.info") return; // 来源校验
console.log("received: " + event.data);
event.source.postMessage("ok", event.origin); // 用 source 直接回信
});
  • 事件成员: data 是消息内容, origin 是发送方源, source 是发送方窗口引用;
  • "*": 等于放弃站点校验, 只在消息完全公开时使用;