上下文隔离

上下文隔离是什么?

上下文隔离功能将确保您的 预加载脚本 和 Electron的内部逻辑 运行在所加载的 webcontent网页 之外的另一个独立的上下文环境里。 这对安全性很重要,因为它有助于阻止网站访问 Electron 的内部组件 和 您的预加载脚本可访问的高等级权限的API 。

这意味着,实际上,您的预加载脚本访问的 window 对象并不是网站所能访问的对象。 例如,如果您在预加载脚本中设置 window.hello = 'wave' 并且启用了上下文隔离,当网站尝试访问window.hello对象时将返回 undefined。

自 Electron 12 以来,默认情况下已启用上下文隔离,并且它是 所有应用程序推荐的安全设置。

迁移

没有上下文隔离,从预加载脚本提供API时,经常会使用window.X = apiObject 那么现在呢?

之前: 上下文隔离禁用

在渲染进程中,预加载脚本暴露给已加载的页面 API 是一个常见的使用方式。 当上下文隔离时,您的预加载脚本可能会暴露一个常见的全局window对象给渲染进程。 此后,您可以从中添加任意的属性到预加载在脚本。

preload.js

  1. // 上下文隔离禁用的情况下使用预加载
  2. window.myAPI = {
  3. doAThing: () => {}
  4. }

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

renderer.js

  1. // 在渲染器进程使用导出的 API
  2. window.myAPI.doAThing()

之后:启用上下文隔离

Electron 提供一种专门的模块来无阻地帮助您完成这项工作。 contextBridge 模块可以用来安全地从独立运行、上下文隔离的预加载脚本中暴露 API 给正在运行的渲染进程。 API 还可以像以前一样,从 window.myAPI 网站上访问。

preload.js

  1. // 在上下文隔离启用的情况下使用预加载
  2. const { contextBridge } = require('electron')
  3. contextBridge.exposeInMainWorld('myAPI', {
  4. doAThing: () => {}
  5. })

renderer.js

  1. // 在渲染器进程使用导出的 API
  2. window.myAPI.doAThing()

请阅读 contextBridge 的文档,以全面了解其限制。 例如,您不能在 contextBridge 中暴露原型或者 Symbol。

安全事项

单单开启和使用 contextIsolation 并不直接意味着您所做的一切都是安全的。 例如,此代码是 不安全的

preload.js

  1. // ❌ 错误使用
  2. contextBridge.exposeInMainWorld('myAPI', {
  3. send: ipcRenderer.send
  4. })

它直接暴露了一个没有任何参数过滤的高等级权限 API 。 这将允许任何网站发送任意的 IPC 消息,这不会是你希望发生的。 相反,暴露进程间通信相关 API 的正确方法是为每一种通信消息提供一种实现方法。

preload.js

  1. // ✅ 正确使用
  2. contextBridge.exposeInMainWorld('myAPI', {
  3. loadPreferences: () => ipcRenderer.invoke('load-prefs')
  4. })

与Typescript一同使用

如果您正在使用 TypeScript 构建 Electron 应用程序,您需要给通过 context bridge 暴露的 API 添加类型。 渲染进程的 window 对象将不会包含正确扩展类型,除非给其添加了 类型声明

例如,在这个 preload.ts 脚本中:

preload.ts

  1. contextBridge.exposeInMainWorld('electronAPI', {
  2. loadPreferences: () => ipcRenderer.invoke('load-prefs')
  3. })

您可以创建一个 renderer.d.ts 类型声明文件,并且全局增强 Window 接口。

renderer.d.ts

  1. export interface IElectronAPI {
  2. loadPreferences: () => Promise<void>,
  3. }
  4. declare global {
  5. interface Window {
  6. electronAPI: IElectronAPI
  7. }
  8. }

以上所做皆是为了确保在您编写渲染进程的脚本时, TypeScript 编译器将会知晓electronAPI合适地在您的全局window对象中

renderer.ts

  1. window.electronAPI.loadPreferences()