安全最佳实践 #
一、安全概述 #
1.1 Electron 安全架构 #
text
┌─────────────────────────────────────────────────────────────┐
│ 安全威胁与防护 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ 渲染进程 │ │ 主进程 │ │
│ │ │ │ │ │
│ │ 隔离环境 │◄──IPC──►│ Node.js │ │
│ │ 沙箱 │ │ 完全权限 │ │
│ └─────────────┘ └─────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ 安全防护措施 │ │
│ │ │ │
│ │ • 上下文隔离 │ │
│ │ • 禁用 Node.js 集成 │ │
│ │ • 内容安全策略 (CSP) │ │
│ │ • 预加载脚本白名单 │ │
│ │ • 安全的 IPC 通信 │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
1.2 安全原则 #
| 原则 | 说明 |
|---|---|
| 最小权限 | 只授予必要的权限 |
| 深度防御 | 多层安全防护 |
| 安全默认 | 默认配置应该是安全的 |
| 输入验证 | 验证所有外部输入 |
二、安全配置 #
2.1 BrowserWindow 安全配置 #
javascript
const mainWindow = new BrowserWindow({
webPreferences: {
// 必须启用的安全选项
nodeIntegration: false, // 禁用 Node.js 集成
contextIsolation: true, // 启用上下文隔离
enableRemoteModule: false, // 禁用 remote 模块
sandbox: true, // 启用沙箱
// 推荐的安全选项
webSecurity: true, // 启用 Web 安全
allowRunningInsecureContent: false, // 禁止加载不安全内容
// 预加载脚本
preload: path.join(__dirname, 'preload.js')
}
});
2.2 内容安全策略 (CSP) #
html
<!-- index.html -->
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' data:;
connect-src 'self' https://api.example.com;
frame-src 'none';
">
javascript
// 或在主进程中设置
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
'Content-Security-Policy': [
"default-src 'self'; " +
"script-src 'self'; " +
"style-src 'self' 'unsafe-inline'"
]
}
});
});
2.3 禁用危险功能 #
javascript
// 禁用导航到外部 URL
mainWindow.webContents.on('will-navigate', (event, url) => {
const parsedUrl = new URL(url);
if (parsedUrl.origin !== 'app://-') {
event.preventDefault();
}
});
// 禁止打开新窗口
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
// 在默认浏览器中打开
shell.openExternal(url);
return { action: 'deny' };
});
// 禁用 webview 标签
const mainWindow = new BrowserWindow({
webPreferences: {
webviewTag: false
}
});
三、预加载脚本安全 #
3.1 安全的预加载脚本 #
javascript
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
// 定义允许的频道白名单
const validChannels = {
invoke: ['get-user', 'save-settings', 'read-file'],
send: ['log-event', 'track-analytics'],
receive: ['update-available', 'notification']
};
// 安全地暴露 API
contextBridge.exposeInMainWorld('electronAPI', {
invoke: (channel, ...args) => {
if (validChannels.invoke.includes(channel)) {
return ipcRenderer.invoke(channel, ...args);
}
throw new Error(`Invalid channel: ${channel}`);
},
send: (channel, ...args) => {
if (validChannels.send.includes(channel)) {
ipcRenderer.send(channel, ...args);
}
},
on: (channel, callback) => {
if (validChannels.receive.includes(channel)) {
const subscription = (event, ...args) => callback(...args);
ipcRenderer.on(channel, subscription);
// 返回取消订阅函数
return () => {
ipcRenderer.removeListener(channel, subscription);
};
}
}
});
3.2 验证数据 #
javascript
// preload.js
function validatePath(filePath) {
const allowedDir = app.getPath('userData');
const resolvedPath = path.resolve(filePath);
if (!resolvedPath.startsWith(allowedDir)) {
throw new Error('Access denied: path outside allowed directory');
}
return resolvedPath;
}
contextBridge.exposeInMainWorld('electronAPI', {
readFile: async (filePath) => {
const safePath = validatePath(filePath);
return ipcRenderer.invoke('read-file', safePath);
}
});
四、IPC 安全 #
4.1 主进程验证 #
javascript
// main.js
const Joi = require('joi');
// 定义验证模式
const schemas = {
'save-user': Joi.object({
name: Joi.string().required(),
email: Joi.string().email().required()
}),
'read-file': Joi.string().required()
};
// IPC 处理器
ipcMain.handle('save-user', async (event, data) => {
// 验证数据
const { error, value } = schemas['save-user'].validate(data);
if (error) {
throw new Error(`Validation error: ${error.message}`);
}
// 处理请求
return await saveUser(value);
});
4.2 来源验证 #
javascript
// 验证请求来源
ipcMain.handle('sensitive-action', (event, data) => {
const webContents = event.sender;
const win = BrowserWindow.fromWebContents(webContents);
// 检查是否是预期的窗口
if (win !== expectedWindow) {
throw new Error('Unauthorized source');
}
// 处理请求
return handleSensitiveAction(data);
});
五、远程内容安全 #
5.1 加载远程内容 #
javascript
// 不推荐:直接加载远程内容
// mainWindow.loadURL('https://example.com');
// 推荐:只加载本地内容
mainWindow.loadFile('index.html');
// 如果必须加载远程内容
const mainWindow = new BrowserWindow({
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
sandbox: true,
webSecurity: true,
preload: path.join(__dirname, 'preload.js')
}
});
// 验证 URL
mainWindow.loadURL('https://trusted-domain.com');
5.2 验证远程内容 #
javascript
// 验证 URL 白名单
const allowedOrigins = [
'https://api.example.com',
'https://cdn.example.com'
];
function isAllowedOrigin(url) {
try {
const parsedUrl = new URL(url);
return allowedOrigins.includes(parsedUrl.origin);
} catch {
return false;
}
}
// 在请求前验证
session.defaultSession.webRequest.onBeforeRequest(
{ urls: ['*://*/*'] },
(details, callback) => {
if (!isAllowedOrigin(details.url)) {
callback({ cancel: true });
} else {
callback({});
}
}
);
六、数据安全 #
6.1 敏感数据存储 #
javascript
const Store = require('electron-store');
const crypto = require('crypto');
// 加密存储
const store = new Store({
encryptionKey: process.env.ENCRYPTION_KEY || 'fallback-key'
});
// 或自定义加密
function encrypt(text, key) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return iv.toString('hex') + ':' + encrypted;
}
function decrypt(text, key) {
const [ivHex, encrypted] = text.split(':');
const iv = Buffer.from(ivHex, 'hex');
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
6.2 安全删除 #
javascript
const fs = require('fs');
// 安全删除文件(多次覆写)
async function secureDelete(filePath, passes = 3) {
const stats = await fs.promises.stat(filePath);
const bufferSize = stats.size;
for (let i = 0; i < passes; i++) {
const randomData = crypto.randomBytes(bufferSize);
await fs.promises.writeFile(filePath, randomData);
}
await fs.promises.unlink(filePath);
}
七、网络安全 #
7.1 HTTPS 强制 #
javascript
// 强制 HTTPS
app.on('web-contents-created', (event, contents) => {
contents.on('will-navigate', (event, navigationUrl) => {
const parsedUrl = new URL(navigationUrl);
if (parsedUrl.protocol !== 'https:') {
event.preventDefault();
}
});
});
7.2 证书验证 #
javascript
// 自定义证书验证(仅用于开发)
if (isDev) {
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
// 开发环境忽略证书错误
event.preventDefault();
callback(true);
});
}
// 生产环境使用系统证书验证
7.3 网络请求安全 #
javascript
const { net } = require('electron');
// 安全的网络请求
async function secureFetch(url, options = {}) {
// 验证 URL
if (!url.startsWith('https://')) {
throw new Error('Only HTTPS is allowed');
}
const request = net.request({
method: options.method || 'GET',
url: url
});
// 设置安全头
request.setHeader('Content-Type', 'application/json');
return new Promise((resolve, reject) => {
request.on('response', (response) => {
let data = '';
response.on('data', (chunk) => { data += chunk; });
response.on('end', () => {
resolve({
status: response.statusCode,
data: data
});
});
});
request.on('error', reject);
request.end();
});
}
八、进程隔离 #
8.1 沙箱进程 #
javascript
const mainWindow = new BrowserWindow({
webPreferences: {
sandbox: true, // 启用沙箱
contextIsolation: true,
nodeIntegration: false
}
});
8.2 隔离敏感操作 #
javascript
// 在单独的渲染进程中运行不受信任的代码
const untrustedWindow = new BrowserWindow({
webPreferences: {
sandbox: true,
contextIsolation: true,
nodeIntegration: false,
webSecurity: true,
allowRunningInsecureContent: false
}
});
// 限制权限
untrustedWindow.webContents.session.setPermissionRequestHandler(
(webContents, permission, callback) => {
// 拒绝所有权限请求
callback(false);
}
);
九、安全检查清单 #
9.1 必须项 #
markdown
- [ ] nodeIntegration: false
- [ ] contextIsolation: true
- [ ] enableRemoteModule: false
- [ ] sandbox: true
- [ ] webSecurity: true
- [ ] 设置 CSP
- [ ] 验证 IPC 数据
- [ ] 频道白名单
9.2 推荐项 #
markdown
- [ ] 禁用 webviewTag
- [ ] 验证远程 URL
- [ ] 加密敏感数据
- [ ] 安全删除敏感文件
- [ ] 使用 HTTPS
- [ ] 限制权限请求
- [ ] 审计第三方依赖
十、总结 #
10.1 核心要点 #
| 要点 | 说明 |
|---|---|
| 上下文隔离 | 必须启用 contextIsolation |
| 禁用 Node.js | 渲染进程禁用 nodeIntegration |
| CSP | 设置内容安全策略 |
| 预加载脚本 | 使用白名单验证频道 |
| 数据验证 | 验证所有 IPC 数据 |
10.2 下一步 #
现在你已经掌握了安全最佳实践,接下来让我们学习 性能优化,深入了解 Electron 应用的性能优化技巧!
最后更新:2026-03-28