Electron IPC and nodeIntegration

45

因此,我跟随了一些指南设置了Webpack、Electron和React来制作桌面应用程序。完成设置后,我开始工作,并了解到我需要从主进程和渲染器中要求IPC机制以进行通信。

import {ipcRenderer} from "electron"; 将此添加到我的renderer.js文件会导致错误Uncaught ReferenceError: require is not defined

在向同事请教后,建议我在main.js文件中更改:

webPreferences: {
    nodeIntegration: false,
}

webPreferences: {
    nodeIntegration: true,
}

我在Google上搜索发现,如果你关心安全问题,那么使用electron ipc不是一个好的选择。但是,我所找到的所有关于electron ipc的资源都使用了ipcRenderer。

那么,是不是每个互联网上的例子都存在巨大的安全漏洞,或者我漏掉了某些重要部分?

我的问题如下:

  1. 是否可能在不启用nodeIntegration的情况下使用ipcRenderer?
  2. 如果可以,我该怎么做,为什么会有如此多的资源排除这些信息?
  3. 如果不行,我应该使用什么方法?

如果我的问题有误,我错过了什么,或者我提出的问题存在其他明显的问题,请让我知道,否则先感谢您的帮助。


2
nodeIntegration 可以启用/禁用 NodeJS 的使用,由于 Electron 是一个 NodeJS 模块,因此您无法在没有 NodeJS 的情况下使用它。因此,如果您想要使用 Electron 的 ipcRenderer,则必须启用 NodeJS。 - Alexander Leithner
1
我同意。如果将 nodeIntegration 设置为 false,则无法在 mainrenderer 进程之间进行通信。我实际上很好奇当它被设置为 false 时,Electron 的实际用途是什么,现在他们已经将其设置为默认值。 - jayarjo
1
@jayarjo 我不理解,因为根据 Electron 文档,nodeIntegration 默认值是 false,所以不需要它吗? - oldboy
nodeIntegration 被禁用时,您仍然可以通过其他方式与主线程通信。例如,您可以建立一个 WebSocket 连接或使用标准的 HTTP 方法(如在后台发送 GET / POST JSON 数据)。Luke H 的下面回答提供了对您三个问题的详细解释和解决方案,我建议将其标记为被接受的答案。 - jacobq
在这个问答中,我发布了一个示例,介绍如何通过预加载脚本设置基于IPC的主进程和渲染器进程之间的通信。希望能对您有所帮助。 - customcommander
@customcommander,当允许nodeintegration时,有些应用程序比其他应用程序更容易受到攻击吗?如果避免某些操作或功能,是否可以允许它? - Vass
1个回答

45
  1. 是否可以在不启用nodeIntegration的情况下使用ipcRenderer?

可以,但比较麻烦。可以通过使用preload脚本来实现。

  1. 如果可以,如何做到这一点,为什么会有这么多资源排除这些信息?

可以通过使用下面指示的preload脚本来实现。然而,这不被视为安全的做法。大部分现有的文档并没有展示最佳的安全实践。

下面给出一个更加安全的例子。

// preload.js
const electron = require('electron');

process.once('loaded', () => {
  global.ipcRenderer = electron.ipcRenderer;
});
// main.js
const {app, BrowserWindow} = require('electron');

app.on('ready', () => {
  // Create the browser window.
  win = new BrowserWindow({
      backgroundColor: '#fff', // always set a bg color to enable font antialiasing!
      webPreferences: {
        preload: path.join(__dirname, './preload.js'),
        nodeIntegration: false,
        enableRemoteModule: false,
        // contextIsolation: true,
        // nativeWindowOpen: true,
        // sandbox: true,
      }
  });
  win.loadURL(`file://${path.join(__dirname, 'index.html')}`);

注意:预加载脚本的路径必须是绝对路径,如果使用webpack/babel可能会变得复杂,因为输出文件可能在不同的路径下。

  1. 如果不是绝对路径,我该用什么?

编辑:如@Yannic所指出的那样,Electron现在支持另一种选项,称为contextBridge。这个新选项可能更简单地解决了问题。有关contextBridge的信息,请查看Electron文档:https://www.electronjs.org/docs/tutorial/context-isolation

但即使使用 contextBridge,您也不应该尝试公开整个Electron API,而只能公开您为您的应用程序设计的受限API

如上所述,虽然可以像上面展示的那样使用ipcRenderer,但当前的Electron安全建议也建议启用contextIsolation。这将使上述方法无法使用,因为您将不能再向全局范围添加数据。

据我所知,最安全的建议是使用addEventListenerpostMessage,并将预加载脚本用作渲染器和主脚本之间的桥梁。

// preload.js
const { ipcRenderer } = require('electron');

process.once('loaded', () => {
  window.addEventListener('message', event => {
    // do something with custom event
    const message = event.data;

    if (message.myTypeField === 'my-custom-message') {
      ipcRenderer.send('custom-message', message);
    }
  });
});
// main.js
const {app, ipcMain, BrowserWindow} = require('electron');

app.on('ready', () => {
  ipcMain.on('custom-message', (event, message) => {
    console.log('got an IPC message', e, message);
  });

  // Create the browser window.
  win = new BrowserWindow({
      backgroundColor: '#fff', // always set a bg color to enable font antialiasing!
      webPreferences: {
        preload: path.join(__dirname, './preload.js'),
        nodeIntegration: false,
        enableRemoteModule: false,
        contextIsolation: true,
        sandbox: true,
        // nativeWindowOpen: true,
      }
  });
  win.loadURL(`file://${path.join(__dirname, 'index.html')}`);
// renderer.js
window.postMessage({
  myTypeField: 'my-custom-message',
  someData: 123,
});

6
请注意,这个配置是为了最大限度地保证安全,假设您的应用程序可能打开外部链接。如果您仔细控制对外部网站的使用,可以使用节点集成来提高一定的安全性。 - Luke H
1
就我所见,最好使用单个预加载并基本上在那里创建一种API。同样,如果您不加载/允许外部站点,则可能可以跳过某些安全性。 - Luke H
3
在这个系统中,你实际上无法使用'require',因为唯一的通信方式是通过静默序列化和反序列化的消息。这意味着你不能按标识传递对象(只能传递数据的副本),并且任何特殊类都会丢失。基本上就像执行JSON.parse(JSON.serialize(...))一样。你只能传递数据。 你需要在主进程中实现任何类似节点的逻辑,并使用消息发送/接收普通数据到渲染器进程。 - Luke H
1
@Yannic 这个答案是在 contextBridge 可用之前编写的。因此,这种方法适用于旧版本的 electron。它可能在幕后实现了类似的方法? - Luke H
1
@MaciejKrawczyk 这是一个很好的问题。我还没来得及深入研究。由于preload附加到BrowserWindow上,我期望Electron会确保preload窗口对象正确连接到此BrowserWindow。值得探究!我不确定这个答案是否仍然适用于现代的Electron。 - Luke H
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接