Electron 开发者的 Tauri 2.0 实战指南:安全实践

devtools/2025/1/20 5:55:17/

在桌面应用开发中,安全性至关重要。相比 Electron,Tauri 2.0 提供了更严格的安全模型和更完善的权限系统。本文将帮助你理解和实践 Tauri 的安全特性。

权限系统对比

Electron 的安全模型

在 Electron 中,我们通常这样处理安全:

// main.js
const { app, BrowserWindow } = require('electron')function createWindow() {const win = new BrowserWindow({width: 800,height: 600,webPreferences: {nodeIntegration: false,contextIsolation: true,sandbox: true}})// 加载本地文件win.loadFile('index.html')
}app.whenReady().then(() => {createWindow()
})

主要特点:

  1. 上下文隔离
  2. 沙箱机制
  3. CSP 策略
  4. 手动配置

Tauri 的安全模型

Tauri 采用了更严格的安全策略:

// main.rs
use tauri::Manager;
use tauri::api::path::app_dir;
use serde::{Deserialize, Serialize};#[derive(Debug, Serialize, Deserialize)]
struct Permissions {fs_access: bool,network_access: bool,shell_access: bool,
}#[tauri::command]
async fn check_permissions(app: tauri::AppHandle
) -> Result<Permissions, String> {let perms = Permissions {fs_access: app.fs_scope().is_allowed("path/to/check"),network_access: app.runtime_handle().is_allowed("network"),shell_access: app.runtime_handle().is_allowed("shell")};Ok(perms)
}fn main() {tauri::Builder::default().setup(|app| {// 配置安全策略app.manage(tauri::SecurityConfig::default().with_csp("default-src 'self'").with_dangerous_allow_asset_csp_modification(false));Ok(())}).invoke_handler(tauri::generate_handler![check_permissions]).run(tauri::generate_context!()).expect("error while running tauri application");
}
// security.ts
import { invoke } from '@tauri-apps/api/tauri'interface Permissions {fs_access: booleannetwork_access: booleanshell_access: boolean
}export const checkPermissions = async (): Promise<Permissions> => {try {return await invoke('check_permissions')} catch (error) {console.error('Failed to check permissions:', error)throw error}
}

实战案例:安全的密码管理器

让我们通过一个实际的案例来演示 Tauri 的安全特性:

// main.rs
use argon2::{password_hash::{rand_core::OsRng,PasswordHash, PasswordHasher, PasswordVerifier, SaltString},Argon2
};
use chacha20poly1305::{aead::{Aead, KeyInit},ChaCha20Poly1305, Nonce
};
use serde::{Deserialize, Serialize};
use std::fs;#[derive(Debug, Serialize, Deserialize)]
struct PasswordEntry {site: String,username: String,encrypted_password: Vec<u8>,nonce: Vec<u8>,
}#[derive(Debug, Serialize, Deserialize)]
struct Vault {entries: Vec<PasswordEntry>,master_hash: String,
}#[tauri::command]
async fn create_vault(master_password: String
) -> Result<(), String> {// 生成 master password hashlet salt = SaltString::generate(&mut OsRng);let argon2 = Argon2::default();let master_hash = argon2.hash_password(master_password.as_bytes(), &salt).map_err(|e| e.to_string())?.to_string();let vault = Vault {entries: Vec::new(),master_hash,};// 保存空保险库save_vault(&vault)?;Ok(())
}#[tauri::command]
async fn add_password(master_password: String,site: String,username: String,password: String
) -> Result<(), String> {// 验证 master passwordlet vault = load_vault()?;let parsed_hash = PasswordHash::new(&vault.master_hash).map_err(|e| e.to_string())?;Argon2::default().verify_password(master_password.as_bytes(), &parsed_hash).map_err(|_| "Invalid master password")?;// 加密密码let key = derive_key(&master_password);let cipher = ChaCha20Poly1305::new(&key.into());let nonce = Nonce::from_slice(&rand::random::<[u8; 12]>());let encrypted_password = cipher.encrypt(nonce, password.as_bytes()).map_err(|e| e.to_string())?;// 添加新条目let mut vault = load_vault()?;vault.entries.push(PasswordEntry {site,username,encrypted_password,nonce: nonce.to_vec(),});save_vault(&vault)?;Ok(())
}#[tauri::command]
async fn get_password(master_password: String,site: String,username: String
) -> Result<String, String> {// 验证 master passwordlet vault = load_vault()?;let parsed_hash = PasswordHash::new(&vault.master_hash).map_err(|e| e.to_string())?;Argon2::default().verify_password(master_password.as_bytes(), &parsed_hash).map_err(|_| "Invalid master password")?;// 查找并解密密码let entry = vault.entries.iter().find(|e| e.site == site && e.username == username).ok_or("Password not found")?;let key = derive_key(&master_password);let cipher = ChaCha20Poly1305::new(&key.into());let nonce = Nonce::from_slice(&entry.nonce);let password = cipher.decrypt(nonce, entry.encrypted_password.as_ref()).map_err(|e| e.to_string())?;String::from_utf8(password).map_err(|e| e.to_string())
}fn derive_key(password: &str) -> [u8; 32] {use sha2::{Sha256, Digest};let mut hasher = Sha256::new();hasher.update(password.as_bytes());hasher.finalize().into()
}fn load_vault() -> Result<Vault, String> {let vault_path = get_vault_path()?;let json = fs::read_to_string(vault_path).map_err(|e| e.to_string())?;serde_json::from_str(&json).map_err(|e| e.to_string())
}fn save_vault(vault: &Vault) -> Result<(), String> {let vault_path = get_vault_path()?;let json = serde_json::to_string_pretty(vault).map_err(|e| e.to_string())?;fs::write(vault_path, json).map_err(|e| e.to_string())
}fn get_vault_path() -> Result<String, String> {let app_dir = app_dir(&tauri::Config::default()).ok_or("Failed to get app directory")?;Ok(app_dir.join("vault.json").to_string_lossy().into_owned())
}
// App.tsx
import { useState, useEffect } from 'react'
import { invoke } from '@tauri-apps/api/tauri'interface PasswordEntry {site: stringusername: string
}function App() {const [masterPassword, setMasterPassword] = useState('')const [site, setSite] = useState('')const [username, setUsername] = useState('')const [password, setPassword] = useState('')const [status, setStatus] = useState('')const [entries, setEntries] = useState<PasswordEntry[]>([])const handleCreateVault = async () => {try {await invoke('create_vault', { masterPassword })setStatus('Vault created successfully')} catch (error) {setStatus(`Error: ${error}`)}}const handleAddPassword = async () => {try {await invoke('add_password', {masterPassword,site,username,password})setStatus('Password added successfully')setPassword('')} catch (error) {setStatus(`Error: ${error}`)}}const handleGetPassword = async () => {try {const password = await invoke('get_password', {masterPassword,site,username})setPassword(password as string)setStatus('Password retrieved successfully')} catch (error) {setStatus(`Error: ${error}`)}}return (<div className="container"><h1>Secure Password Manager</h1><div className="form-group"><inputtype="password"placeholder="Master Password"value={masterPassword}onChange={(e) => setMasterPassword(e.target.value)}/><button onClick={handleCreateVault}>Create Vault</button></div><div className="form-group"><inputtype="text"placeholder="Site"value={site}onChange={(e) => setSite(e.target.value)}/><inputtype="text"placeholder="Username"value={username}onChange={(e) => setUsername(e.target.value)}/><inputtype="password"placeholder="Password"value={password}onChange={(e) => setPassword(e.target.value)}/><button onClick={handleAddPassword}>Add Password</button><button onClick={handleGetPassword}>Get Password</button></div><div className="status">{status}</div></div>)
}export default App
/* styles.css */
.container {padding: 20px;max-width: 800px;margin: 0 auto;
}.form-group {margin: 20px 0;display: flex;flex-direction: column;gap: 10px;
}input {padding: 10px;border: 1px solid #ddd;border-radius: 4px;
}button {padding: 10px 20px;background: #007bff;color: white;border: none;border-radius: 4px;cursor: pointer;
}button:hover {background: #0056b3;
}.status {margin-top: 20px;padding: 10px;border-radius: 4px;background: #f8f9fa;
}

安全最佳实践

  1. 权限控制

    • 最小权限原则
    • 精确的权限范围
    • 动态权限请求
    • 权限审计日志
  2. 数据加密

    • 使用强加密算法
    • 安全的密钥管理
    • 加密传输数据
    • 安全存储凭证
  3. 输入验证

    • 验证所有输入
    • 防止注入攻击
    • 限制输入长度
    • 过滤特殊字符
  4. 安全配置

    • 严格的 CSP
    • 禁用危险特性
    • 更新依赖包
    • 安全的默认值

调试与审计

  1. 安全日志

    use log::{info, warn, error};#[tauri::command]
    async fn security_audit(action: String) -> Result<(), String> {info!("Security audit: {}", action);Ok(())
    }
  2. 权���检查

    #[tauri::command]
    async fn check_security_config(app: tauri::AppHandle
    ) -> Result<String, String> {let config = app.security_config();Ok(format!("Security config: {:?}", config))
    }
  3. 性能监控

    use std::time::Instant;#[tauri::command]
    async fn measure_security_operation() -> Result<String, String> {let start = Instant::now();// 执行安全操作let duration = start.elapsed();Ok(format!("Operation took: {:?}", duration))
    }

小结

  1. Tauri 安全优势:

    • 更严格的权限模型
    • 内置的安全特性
    • 更好的隔离机制
    • 更现代的加密方案
  2. 安全策略:

    • 实施权限控制
    • 加密敏感数据
    • 验证所有输入
    • 记录安全日志
  3. 最佳实践:

    • 遵循最小权限
    • 使用安全配置
    • 定期安全审计
    • 及时更新依赖

下一篇文章,我们将探讨 Tauri 2.0 的性能优化,帮助你构建更快速、更高效的桌面应用。

如果觉得这篇文章对你有帮助,别忘了点个赞 👍


http://www.ppmy.cn/devtools/152012.html

相关文章

Linux电源管理——Device Power Management Interface

目录 前言 1、device PM callbacks 2、dev_pm_ops 结构体 3、设备模型中的 dev_pm_ops 4、调用流程 5、platform bus suspend 6、suspend virtio_mmio driver 7、总结 References Linux Version&#xff1a;linux-5.4.239 前言 在一个操作系统中&#xff0c;外部设备…

Java 基础线程篇

一、线程声明及线程间通信 package org.example;import java.util.Random;public class ThreadTest {public void test1() {Thread t1 new Thread(()->{System.out.println("t1...");});Thread t2 new Thread(()->{System.out.println("t2...");}…

SpringBoot+Vue小区智享物业管理系统(高质量源码,可定制,提供文档,免费部署到本地)

作者简介&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流。✌ 主要内容&#xff1a;&#x1f31f;Java项目、Python项目、前端项目、PHP、ASP.NET、人工智能…

三台 Centos7.9 中 Docker 部署 Redis 哨兵模式

三台 Centos7.9 中 Docker 部署 Redis 哨兵模式 1. 环境规划2. 配置 Docker Compose3. 配置 Redis 密码和持久化4. 配置哨兵5. 启动服务6. 验证 Redis 哨兵模式7. 注意事项 1. 环境规划 三台服务器的角色分配如下&#xff1a; IP Address容器端口角色192.168.15.128redis-mas…

Leetcode - 周赛431

目录 一&#xff0c;3411. 最长乘积等价子数组 二&#xff0c;3412. 计算字符串的镜像分数 三&#xff0c;3413. 收集连续 K 个袋子可以获得的最多硬币数量 四&#xff0c;3414. 不重叠区间的最大得分 一&#xff0c;3411. 最长乘积等价子数组 本题数据范围小&#xff0c;直…

51单片机——DS18B20温度传感器

由于DS18B20数字温度传感器是单总线接口&#xff0c;所以需要使用51单片机的一个IO口模拟单总线时序与DS18B20通信&#xff0c;将检测的环境温度读取出来 1、DS18B20模块电路 传感器接口的单总线管脚接至单片机P3.7IO口上 2、DS18B20介绍 2.1 DS18B20外观实物图 管脚1为GN…

【linux命令】ip命令使用

1、设置网口IP 方法1&#xff1a;通过IP设置网口ip 添加静态IP&#xff1a; ip addr add 1.1.1.1/24 dev eth0 删除ip: ip addr del 1.1.1.1/24 dev eth0 方法2&#xff1a;nmtui 配置IP另外方法&#xff1a; nmtui 2、添加路由 添加路由&#xff1a; ip route add 目标网…

RV1126+FFMPEG推流项目(9)AI和AENC模块绑定,并且开启线程采集

前面两篇已经交代AI和AENC模块的配置&#xff0c;这篇就让这两个模块绑定起来&#xff0c;绑定的原因是&#xff0c;Aenc从Ai模块拿到采集的原始数据进行编码。 使用 RK_MPI_SYS_Bind 把 AI 节点和 AENC 进行绑定&#xff0c;其中 enModId 是模块 ID 号选择的是 RK_ID_AI、s32C…