安全最佳实践 #

一、安全概述 #

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