编辑2022
我已经发表了一篇关于Electron历史的大文章,它的安全性提供了影响不同框架版本安全性的变化的额外背景(以及采取的最佳方法)。
原来的答案
我希望这个答案能引起一些注意,因为这里的大多数答案在你的电子应用程序中留下了很大的安全漏洞。事实上,这个答案本质上是你在电子应用程序中使用require()时应该做的事情。(只是有一个新的电子API,使它在v7中更干净一点)。
我写了一个详细的解释/解决方案在github使用最新的电子api,你可以要求()的东西,但我将简要解释为什么你应该遵循一个方法使用预加载脚本,contextBridge和ipc。
这个问题
Electron应用程序很棒,因为我们可以使用node,但这种功能是一把双刃剑。如果我们不小心,我们就会让别人通过我们的应用程序访问节点,而有了节点,坏人就可以破坏你的机器或删除你的操作系统文件(我想还有其他事情)。
As brought up by @raddevus in a comment, this is necessary when loading remote content. If your electron app is entirely offline/local, then you are probably okay simply turning on nodeIntegration:true. I still would, however, opt to keep nodeIntegration:false to act as a safeguard for accidental/malicious users using your app, and prevent any possible malware that might ever get installed on your machine from interacting with your electron app and using the nodeIntegration:true attack vector (incredibly rare, but could happen)!
问题是什么样子的
当您(以下任何一种情况):
是否启用了nodeIntegration:true
使用远程模块
所有这些问题都使你的呈现进程不间断地访问节点。如果您的渲染进程被劫持,您可以认为一切都丢失了。
我们的解决方案是什么
解决方案是不给渲染器直接访问节点(即。Require()),但是为了让我们的电子主进程访问Require,并且在我们的渲染进程需要使用Require时,将一个请求编组到主进程。
在Electron的最新版本(7+)中,我们在渲染端设置了ipcRenderer绑定,在主端设置了ipcMain绑定。在ipcMain绑定中,我们设置了使用所需模块()的侦听器方法。这很好,因为我们的主进程可以要求它想要的一切。
我们使用contextBridge将ipcRenderer绑定传递给我们的应用程序代码(使用),因此当我们的应用程序需要在main中使用所需的模块时,它通过IPC(进程间通信)发送消息,主进程运行一些代码,然后我们将我们的结果发送回消息。
大致上,这就是你要做的。
main.js
const {
app,
BrowserWindow,
ipcMain
} = require("electron");
const path = require("path");
const fs = require("fs");
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win;
async function createWindow() {
// Create the browser window.
win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: false, // is default value after Electron v5
contextIsolation: true, // protect against prototype pollution
enableRemoteModule: false, // turn off remote
preload: path.join(__dirname, "preload.js") // use a preload script
}
});
// Load app
win.loadFile(path.join(__dirname, "dist/index.html"));
// rest of code..
}
app.on("ready", createWindow);
ipcMain.on("toMain", (event, args) => {
fs.readFile("path/to/file", (error, data) => {
// Do something with file contents
// Send result back to renderer process
win.webContents.send("fromMain", responseObj);
});
});
preload.js
const {
contextBridge,
ipcRenderer
} = require("electron");
// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld(
"api", {
send: (channel, data) => {
// whitelist channels
let validChannels = ["toMain"];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
receive: (channel, func) => {
let validChannels = ["fromMain"];
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
}
}
);
index . html
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8"/>
<title>Title</title>
</head>
<body>
<script>
window.api.receive("fromMain", (data) => {
console.log(`Received ${data} from main process`);
});
window.api.send("toMain", "some data");
</script>
</body>
</html>
免责声明
我是secure- electronic -template的作者,这是一个构建电子应用程序的安全模板。我很关心这个话题,并且已经为此工作了几个星期(在这个时间点上)。