上下文隔离

上下文隔离是什么?

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

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

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

迁移

Without context isolation, I used to provide APIs from my preload script using window.X = apiObject. Now what?

之前: 上下文隔离禁用

Exposing APIs from your preload script to a loaded website in the renderer process is a common use-case. With context isolation disabled, your preload script would share a common global window object with the renderer. You could then attach arbitrary properties to a preload script:

preload.js

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

The doAThing() function could then be used directly in the renderer process:

renderer.js

  1. // 在渲染器中使用开放的 API
  2. window.myAPI.doAThing()

之后:启用上下文隔离

There is a dedicated module in Electron to help you do this in a painless way. The contextBridge module can be used to safely expose APIs from your preload script’s isolated context to the context the website is running in. 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()

Please read the contextBridge documentation linked above to fully understand its limitations. For instance, you can’t send custom prototypes or symbols over the bridge.

安全事项

单单开启和使用 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一同使用

If you’re building your Electron app with TypeScript, you’ll want to add types to your APIs exposed over the context bridge. The renderer’s window object won’t have the correct typings unless you extend the types with a declaration file.

For example, given this preload.ts script:

preload.ts

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

You can create a renderer.d.ts declaration file and globally augment the Window interface:

renderer.d.ts

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

Doing so will ensure that the TypeScript compiler will know about the electronAPI property on your global window object when writing scripts in your renderer process:

renderer.ts

  1. window.electronAPI.loadPreferences()