Window.postMessage()
有一个 targetOrigin
参数,可以设置为 URI(以确保消息仅到达特定的 url)。当然也可以设置为*
(不推荐),但是有没有办法指定允许的多个URI?
目前,我只是为每个域发出一个
postMessage()
调用,但这至少可以说有点老套。
不幸的是你不能。您应该提供“*”或单个指定的域。
您可以尝试多次发送,每个域一次:
targetWindow.postMessage(message, "https://domain1.com");
targetWindow.postMessage(message, "http://localhost");
⚠ 不建议使用“*”,防止安全漏洞。
也可以制作数组+循环
虽然Juanmabs22的答案应该work™,但这也会针对每个不匹配的源触发无法捕获的控制台错误消息,这对于那些希望其网站根本不产生错误消息的人来说可能不是最佳选择。
对于那些仍然想要安全通信通道的人(因此不想要不安全的通配符解决方案),有一个无可否认的麻烦的解决方法:
在发送敏感数据之前,您可以设置通过不安全的
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 的 null 起源 iframe 是一个非常糟糕的例子,所以这里是一个外包的 JSFiddle。
我也有同样的问题。我的页面期望发现自己是父页面的子页面(iframe) - 其域可能是已知域列表之一(大约 5-6 个)。
我的解决方案是让家长发送其所属域的提示。如果提示在列表中,则会检查此选项,并且子项会成功初始化。然后,它会在子级生命周期的剩余时间内以该起源为目标。如果父级欺骗了它的域,那么不会造成任何伤害,因为由于目标源不匹配,发回的消息永远不会到达它。
如果您可以访问父母和孩子,此解决方案可能适合您。