背景

在一个已经维护很久的系统中,突然产品说功能 10 点之后不能用了。项目是被第三方通过 iframe 嵌入的一个页面表单操作。在和后台经过一天的友好交流(被甩锅)和查找问题原因(互相甩锅)之后,发现并不是我和后台的问题,在查找问题原因中学习到了 iframe 中设置 sandbox 属性可以起到的一些作用。所以记录一下。

具体现象

当时进入页面后初始化相关的接口都请求不通,产品说后台说是我的问题(又被甩锅),我看了下发现都是 cors 跨域问题。然后发截图给后台,如此这般这般如此,battle 到下午。在排查问题中发现有段不是造成产生问题原因的代码被我发布到了线上,然后就顺手给恢复到了最开始的状态,是一段 localstorage.getItem 的代码被我覆盖成了一个固定值。然后就重新恢复了发布上去了。结果出现了接口没有请求的问题,点击事件也不执行,只出现了 Blocked form submission to '' because the form's frame is sandboxed and the 这样一段报错提示。把刚才改的 localstorage.getItem 这段重新注释掉发布上去以后接口就可以重新请求了。然后开始查看第三方页面 iframe 发现有个 sandbox 为 allow-scripts。怀疑是不是这个导致的,就去问了下第三方,他们排查后说是因为我们只是续缴费了但是相关功能都还没开通。在给我们开通后重新登陆业务账号发现已经没有了 sandbox 属性一切也正常了。

iframe 的 sandbox 属性

在 mdn 上查找相关文档描述如下:

sandbox

该属性对呈现在 iframe 框架中的内容启用一些额外的限制条件。属性值可以为空字符串(这种情况下会启用所有限制),也可以是用空格分隔的一系列指定的字符串。

有效的值有:

  • allow-scripts: 允许嵌入的浏览上下文运行脚本(但不能创建弹窗)。如果没有使用该关键字,就无法运行脚本。
  • allow-forms: 允许嵌入的浏览上下文提交表单。如果没有使用该关键字,则无法提交表单。
  • allow-downloads-without-user-activation : 允许在没有征求用户同意的情况下下载文件。
  • allow-modals: 允许嵌入的浏览上下文打开模态窗口。
  • allow-popups: 允许弹窗 (例如 window.open, target="_blank", showModalDialog)。如果没有使用该关键字,相应的功能将自动被禁用。
  • allow-same-origin: 如果没有使用该关键字,嵌入的浏览上下文将被视为来自一个独立的源,这将使 same-origin policy 同源检查失败。
  • allow-storage-access-by-user-activation : 允许嵌入的浏览上下文通过 Storage Access API 使用父级浏览上下文的存储功能。
  • allow-top-navigation-by-user-activation: 允许嵌入的浏览上下文在经过用户允许后导航(加载)内容到顶级的浏览上下文。
  • allow-storage-access-by-user-activation : 允许嵌入的浏览上下文通过 Storage Access API 使用父级浏览上下文的存储功能。

项目实践

我们在本地启动两个项目,A 项目嵌入 B 项目的一个页面地址。先不添加 sandbox 属性

1
2
<!-- A项目页面 -->
<iframe src="http://localhost:8080/" frameborder="0"></iframe>
1
2
3
4
5
6
7
8
// B项目

//发送一个mock请求
fetch(`http://localhost:9010/api/auth/test?val=${localStorage.getItem("val")}`)
.then((response) => response.text())
.then((data) => console.log("mock接口返回:", data));
//打印一下localStorage值中的值
console.log("获取App页面设置的localStorage值:", localStorage.getItem("test"));

可以看到运行在 http://localhost:3000 上的 A 项目 正常显示运行在 http://localhost:8080/的 B 项目,获取 localstorage 的值和接口请求都正常

触发一下点击事件,可以看到也正常触发

那么我们添加上 sandbox 属性呢

1
2
3
4
5
6
<!-- A项目页面 -->
<iframe
src="http://localhost:8080/"
sandbox="allow-scripts"
frameborder="0"
></iframe>

页面可以正常显示但接口请求没有返回 localstorage 也无法获取。

此时可以排查出问题原因就在于第三方在 iframe 上设置了 sandbox="allow-scripts",影响到了我们项目的使用。
实际上。接口请求还是可以请求通的,只是我们在项目 axios 接口请求时添加了 header 从 localstorage 中获取的值,因而导致了接口请求没有执行。

1
2
3
4
5
6
7
//修改B项目的fetch请求

fetch(`http://localhost:9010/api/auth/test`)
.then((response) => response.text())
.then((data) => console.log("mock接口返回:", data));
//打印一下localStorage值中的值
console.log("获取App页面设置的localStorage值:", localStorage.getItem("test"));

可以看到接口正常返回。但 localstorage 还是无法获取。

在不同源下,如果想和不添加 sandbox 一样正常,需要按照报错提示配置 sandbox="allow-scripts allow-same-origin"属性,就可以恢复正常了。