Window.postMessage()
方法包含一个 targetOrigin
参数,用于指定目标的 URI 地址(以确保消息只能被特定 URL 接收)。当然也可以将其设置为 *
(不推荐),但是否有一种方式可以指定允许多个 URI 地址呢?
目前,我只是针对每个域名分别触发一个 postMessage()
调用,但这似乎至少有点 hacky(繁琐的、笨拙的)。
Window.postMessage()
方法包含一个 targetOrigin
参数,用于指定目标的 URI 地址(以确保消息只能被特定 URL 接收)。当然也可以将其设置为 *
(不推荐),但是否有一种方式可以指定允许多个 URI 地址呢?
目前,我只是针对每个域名分别触发一个 postMessage()
调用,但这似乎至少有点 hacky(繁琐的、笨拙的)。
很遗憾,您不能这样做。您应该提供“*”或单个指定的域名。
targetWindow.postMessage(message, "https://domain1.com");
targetWindow.postMessage(message, "http://localhost");
⚠ 不建议使用“*”来防止安全漏洞。
您也可以使用数组 + 循环
targetOrigin
参数与targetWindow.origin
不完全匹配,postMessage
将抛出一个DOMException
。所以每次调用postMessage
都需要用try/catch
包裹起来,否则只会尝试第一个源(https://domain1.com
)。 - Dai虽然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。