在postmessage中指定多个targetOrigin URI

28

Window.postMessage() 方法包含一个 targetOrigin 参数,用于指定目标的 URI 地址(以确保消息只能被特定 URL 接收)。当然也可以将其设置为 *(不推荐),但是否有一种方式可以指定允许多个 URI 地址呢?

目前,我只是针对每个域名分别触发一个 postMessage() 调用,但这似乎至少有点 hacky(繁琐的、笨拙的)。


http://www.mograblog.com/2013/02/how-to-make-post-message-support-multi-origins-and-multi-messages.html - Marvin
3个回答

25

很遗憾,您不能这样做。您应该提供“*”或单个指定的域名。


感谢澄清。我怀疑可能是这种情况,但想要确认一下。 - Michael Berry
1
请记住,如果您使用“*”通过postMessage发送诸如身份验证令牌之类的有价值数据,则攻击者可以打开您的页面并将数据发送给他们。 - Jespertheend

18
你可以尝试多次发送,每个域名发送一次:
targetWindow.postMessage(message, "https://domain1.com");
targetWindow.postMessage(message, "http://localhost");

⚠ 不建议使用“*”来防止安全漏洞。

您也可以使用数组 + 循环


如果targetOrigin参数与targetWindow.origin不完全匹配,postMessage将抛出一个DOMException。所以每次调用postMessage都需要用try/catch包裹起来,否则只会尝试第一个源(https://domain1.com)。 - Dai

3

虽然Juanmabs22的答案应该可以正常工作™,但这也会触发一个无法捕获的控制台错误消息,因为源不匹配,这对于那些希望网站不产生任何错误消息的人来说可能并不理想。

对于那些仍然希望拥有安全通信渠道(因此不想使用不安全的通配符解决方案)的人来说,有一个明显繁琐的解决方法:
在发送敏感数据之前,您可以通过不安全的targetOrigin:“*”建立握手协商。在第一条消息中,您将请求嵌入者的来源,只有当此来源与您预期的来源匹配时,您才会使用该来源启动安全通信。这意味着嵌入者必须公开其来源,但他们可以使用MessageEvent#origin属性检查您的消息是否来自您的脚本来源。

所以这将给出:

在embeddee.html(您的脚本)中:

async function initComm(allowedOrigins = []){
  const unsafe = new MessageChannel();
  parent.postMessage("negotiation", {
    targetOrigin: "*", // we're using the unsafe wildcard only for this request
    transfer: [unsafe.port1]
  });
  // In case we're talking to someone who doesn't know about our handshake
  const timeout = AbortSignal.timeout(100);
  const targetOrigin = await Promise.race([
    new Promise((res, rej) => timeout.onabort = (evt) => {
      unsafe.port2.close(); // clean
      rej(timeout.reason);
    }),
    // Wait for a response
    new Promise((res) => unsafe.port2.onmessage = ({data}) => res(data))
  ]);
  unsafe.port2.close();
  if (!allowedOrigins.includes(targetOrigin)) {
    throw new Error("Unknown origin: " + targetOrigin);
  }
  const safe = new MessageChannel();
  // Even if they lied, we're using the safe targetOrigin option
  parent.postMessage("begin", { targetOrigin, transfer: [safe.port1] });
  return safe.port2;
}
const safePort = await initComm(["https://embedder-1.com", "https://another-origin.com"]);
// now you can use this MessageChannel to send sensitive data
safePort.postMessage("Here is a secret");

而在嵌入者的一侧:

onmessage = ({origin, data, ports}) => {
  if (origin === YOUR_SCRIPT_ORIGIN) { // only if they recognize you
    switch(data) {
      case "negotiation":
      ports[0].postMessage(window.origin);
      break;
    case "begin":
      beginComm(ports[0]);
      break;
    }
  }
}
function beginComm(safePort) {
  safePort.onmessage = ({data}) => {
    // Now they can handle your messages
    console.log("Safely received a new message", data);
  };
}

很不幸,StackSnippet的空源iframe并不是一个很好的例子,所以在这里有一个外包的JSFiddle


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接