如何在跨域iframe中启用WebUSB且不共享权限
问题描述
在包含跨源沙箱iframe的页面中调试WebUSB时,默认配置下顶层页面授权的WebUSB设备会自动共享给所有开启了usb权限的iframe,不符合「顶层页面与嵌入上下文均可使用WebUSB,但权限完全隔离,每个上下文必须独立调用requestDevice获取访问权限」的需求。
测试环境代码如下:
顶层页面代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Top</title> </head> <body> <button id="button">request</button> <!-- 跨域iframe,代码见下方 --> <iframe sandbox="allow-scripts" allow="usb" src="https://.../sub-frame.html"></iframe> <script> const button = document.getElementById('button'); button.addEventListener('click', async () => { const device = await navigator.usb.requestDevice({ filters: [] }); console.log(device); }); </script> </body> </html>
跨源子框架页面代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Embedded</title> </head> <body> <button id="button">Log</button> <script> const button = document.getElementById('button'); button.addEventListener('click', async () => { const devices = await navigator.usb.getDevices(); console.log(devices); }); </script> </body> </html>
实际测试中,顶层页面完成requestDevice授权后,iframe直接调用navigator.usb.getDevices()就能拿到已授权设备列表。尝试配置allow="usb 'self'"会导致iframe完全无法访问WebUSB API,查阅规范未找到明确的隔离配置方案。
解决方案
Chrome默认将同一顶级页面下所有开启usb权限的上下文的WebUSB授权绑定在顶级页面源上,因此会出现跨源iframe继承权限的问题,可通过以下两种方式实现完全隔离:
高版本Chrome(110+)最简方案
给iframe添加credentialless属性,让iframe运行在独立的无凭据分区上下文,该上下文不会继承顶层页面的任何权限、存储、授权状态,仅需保留原有allow="usb"配置即可正常使用WebUSB能力,且必须独立调用requestDevice完成授权。配置示例:<iframe sandbox="allow-scripts" allow="usb" credentialless src="https://.../sub-frame.html"> </iframe>该配置下,iframe内的WebUSB授权状态完全独立,既不会读取顶层页面的已授权设备,授权结果也不会同步给顶层或其他同页iframe。
低版本兼容方案
给iframe加载流程增加一次跨源中间跳转:先加载一个与顶层页面、目标子框架页面均不同源的中间空白页,再通过中间页重定向到目标子框架地址。Chrome会给经过第三方跳转的跨源iframe分配独立的权限存储分区,不会继承顶层页面的WebUSB授权。
注意:
allow="usb 'self'"配置失效的原因是权限策略中的'self'指向顶层页面源,而非iframe自身源,跨源场景下使用该配置会直接拦截iframe的WebUSB访问,不要使用该写法。
内容的提问来源于stack exchange,提问作者Matt Bierner

