跳到主要内容

上下文隔离

它是什么?

上下文隔离是一项功能,它确保您的 preload 脚本和 Electron 的内部逻辑运行在一个独立的上下文环境中,与您在 webContents 中加载的网站隔离开来。这对于安全目的非常重要,因为它有助于防止网站访问 Electron 内部或您的 preload 脚本有权访问的强大 API。

这意味着您的 preload 脚本有权访问的 window 对象实际上与网站有权访问的对象是不同的。例如,如果您在 preload 脚本中设置了 window.hello = 'wave' 并启用了上下文隔离,则如果网站尝试访问 window.hello,它的值将是 undefined。

自 Electron 12 起,上下文隔离默认启用,它是所有应用程序的推荐安全设置。

迁移

如果没有上下文隔离,我过去常常使用 window.X = apiObject 从我的 preload 脚本提供 API。现在该怎么办?

之前:上下文隔离禁用

在渲染进程中将 API 从 preload 脚本暴露给加载的网站是一个常见的用例。当上下文隔离禁用时,您的 preload 脚本将与渲染器共享一个共同的全局 window 对象。然后,您可以将任意属性附加到 preload 脚本上

preload.js
// preload with contextIsolation disabled
window.myAPI = {
doAThing: () => {}
}

然后,doAThing() 函数就可以直接在渲染进程中使用。

renderer.js
// use the exposed API in the renderer
window.myAPI.doAThing()

之后:上下文隔离启用

Electron 中有一个专门的模块可以帮助您轻松实现这一点。contextBridge 模块可用于安全地将 API 从 preload 脚本的隔离上下文暴露给网站正在运行的上下文。网站也可以像以前一样通过 window.myAPI 访问该 API。

preload.js
// preload with contextIsolation enabled
const { contextBridge } = require('electron')

contextBridge.exposeInMainWorld('myAPI', {
doAThing: () => {}
})
renderer.js
// use the exposed API in the renderer
window.myAPI.doAThing()

请阅读上面链接的 contextBridge 文档以充分了解其限制。例如,您不能通过桥接发送自定义原型或 Symbol。

安全注意事项

仅启用 contextIsolation 和使用 contextBridge 并不能自动保证您所做的一切都是安全的。例如,这段代码是不安全的。

preload.js
// ❌ Bad code
contextBridge.exposeInMainWorld('myAPI', {
send: ipcRenderer.send
})

它直接暴露了一个强大的 API,没有任何参数过滤。这将允许任何网站发送任意的 IPC 消息,而这是您不希望发生的。正确暴露基于 IPC 的 API 的方式应该是为每个 IPC 消息提供一个方法。

preload.js
// ✅ Good code
contextBridge.exposeInMainWorld('myAPI', {
loadPreferences: () => ipcRenderer.invoke('load-prefs')
})

配合 TypeScript 使用

如果您使用 TypeScript 构建 Electron 应用程序,您会希望为您通过上下文桥接暴露的 API 添加类型。渲染器的 window 对象将不会有正确的类型定义,除非您使用声明文件扩展类型。

例如,给定这个 preload.ts 脚本

preload.ts
contextBridge.exposeInMainWorld('electronAPI', {
loadPreferences: () => ipcRenderer.invoke('load-prefs')
})

您可以创建一个 interface.d.ts 声明文件,并全局扩展 Window 接口

interface.d.ts
export interface IElectronAPI {
loadPreferences: () => Promise<void>,
}

declare global {
interface Window {
electronAPI: IElectronAPI
}
}

这样做将确保 TypeScript 编译器在您编写渲染进程中的脚本时,知道您的全局 window 对象上的 electronAPI 属性

renderer.ts
window.electronAPI.loadPreferences()