postMessage 跨域通信

使用 window.postMessage 实现跨域窗口通信

问题

如何在不同源的窗口(如 iframe)之间安全地传递消息?

解答

什么是 postMessage

window.postMessage() 方法提供了一种受控的跨源通信机制。它可以安全地在不同源的窗口之间传递数据,只要正确使用就很安全。

语法

otherWindow.postMessage(message, targetOrigin, [transfer]);

参数说明:

  • message: 要发送的数据
  • targetOrigin: 目标窗口的源,如 "https://example.org"
  • transfer: 可选,传递的对象所有权

接收消息的事件对象

window.addEventListener('message', function(event) {
  // event.data - 传递过来的数据
  // event.origin - 发送方的源(协议 + 域名 + 端口)
  // event.source - 发送方窗口的引用
});

使用示例

子框架发送消息:

// 子框架向父框架发送消息
function sendToParent(msg) {
  var parentUrl = window.parent.location.origin;
  window.parent.postMessage(msg, parentUrl);
}

window.onload = function() {
  sendToParent('Hello from iframe');
}

父框架接收消息:

window.addEventListener('message', function(e) {
  // 验证来源
  if (e.origin !== 'http://example.com:8080') {
    return;
  }
  
  console.log('收到消息:', e.data);
  console.log('来自:', e.origin);
});

双向通信示例

A 窗口(发送方):

// A窗口域名: http://example.com:8080
var popup = window.open('http://example.org/popup.html');

// 等待弹窗加载完成后发送消息
popup.postMessage(
  "The user is 'bob' and the password is 'secret'",
  "http://example.org"  // 必须指定精确的目标源
);

B 窗口(接收方):

// B窗口域名: http://example.org
function receiveMessage(event) {
  // 验证消息来源
  if (event.origin !== "http://example.com:8080") {
    return;
  }
  
  // 处理消息
  console.log('接收到的数据:', event.data);
  
  // 可以通过 event.source 回复消息
  event.source.postMessage('收到消息', event.origin);
}

window.addEventListener('message', receiveMessage);

安全注意事项

1. 始终验证消息来源:

window.addEventListener('message', function(event) {
  // 检查 origin
  if (event.origin !== 'https://trusted-site.com') {
    return;  // 拒绝不信任的来源
  }
  
  // 验证消息格式
  if (typeof event.data !== 'object' || !event.data.type) {
    return;
  }
  
  // 处理消息
});

2. 指定精确的 targetOrigin:

// ❌ 不安全 - 任何窗口都能接收
otherWindow.postMessage(data, '*');

// ✅ 安全 - 只有指定源能接收
otherWindow.postMessage(data, 'https://example.com');

关键点

  • postMessage 用于不同源窗口之间的安全通信,常见于 iframe、弹窗等场景
  • 发送消息时必须指定精确的 targetOrigin,避免使用 '*'
  • 接收消息时必须验证 event.origin,拒绝不信任的来源
  • 接收到的数据也需要验证格式和内容,防止 XSS 攻击
  • 可以通过 event.source 实现双向通信