Tauri 安全机制
安全架构
┌─────────────────────────────────────────────────────┐
│ Frontend │
│ (受限的 Web 环境) │
└─────────────────────────┬───────────────────────────┘
│ 权限检查
┌─────────────────────────▼───────────────────────────┐
│ Tauri Permission System │
│ ┌──────────────────────────────────────────────────┐│
│ │ Capabilities (能力集) → Permissions (权限) ││
│ │ → Scopes (作用域限制) ││
│ └──────────────────────────────────────────────────┘│
└─────────────────────────┬───────────────────────────┘
│ 验证通过
┌─────────────────────────▼───────────────────────────┐
│ Rust Backend │
│ (安全的系统操作) │
└─────────────────────────────────────────────────────┘权限系统
定义 Capabilities
json
// src-tauri/capabilities/main.json
{
"identifier": "main-capability",
"description": "Main window capabilities",
"windows": ["main"],
"permissions": [
"core:default",
"shell:allow-open",
{
"identifier": "fs:allow-read",
"allow": [
{ "path": "$APPDATA/**" },
{ "path": "$DOCUMENT/**" }
]
},
{
"identifier": "fs:allow-write",
"allow": [
{ "path": "$APPDATA/**" }
]
},
"http:default"
]
}权限作用域
json
// 文件系统权限限制
{
"identifier": "fs:allow-read",
"allow": [
{ "path": "$APPDATA/**" }, // 应用数据目录
{ "path": "$DOCUMENT/myapp/**" } // 文档目录下特定文件夹
],
"deny": [
{ "path": "$HOME/.ssh/**" } // 禁止读取 SSH 密钥
]
}CSP 配置
json
// tauri.conf.json
{
"app": {
"security": {
"csp": {
"default-src": "'self'",
"script-src": "'self'",
"style-src": "'self' 'unsafe-inline'",
"img-src": "'self' data: https:",
"connect-src": "'self' https://api.example.com",
"font-src": "'self' data:"
}
}
}
}IPC 安全
Command 权限验证
rust
use tauri::{command, AppHandle, Manager};
// 定义权限插件
#[command]
async fn admin_action(app: AppHandle) -> Result<(), String> {
// 验证调用来源
let window = app.get_window("main").ok_or("Invalid window")?;
// 检查用户权限
let state = app.state::<AuthState>();
if !state.is_admin() {
return Err("Permission denied".to_string());
}
// 执行敏感操作
Ok(())
}输入验证
rust
use validator::Validate;
#[derive(Validate, serde::Deserialize)]
struct CreateUserInput {
#[validate(email)]
email: String,
#[validate(length(min = 8, max = 128))]
password: String,
#[validate(length(max = 100))]
name: String,
}
#[command]
fn create_user(input: CreateUserInput) -> Result<(), String> {
input.validate().map_err(|e| e.to_string())?;
// 处理已验证的输入
Ok(())
}安全存储
rust
use tauri_plugin_store::StoreBuilder;
use keyring::Entry;
// 使用系统密钥链存储敏感数据
fn store_secret(service: &str, key: &str, value: &str) -> Result<(), String> {
let entry = Entry::new(service, key)
.map_err(|e| e.to_string())?;
entry.set_password(value)
.map_err(|e| e.to_string())?;
Ok(())
}
fn get_secret(service: &str, key: &str) -> Result<String, String> {
let entry = Entry::new(service, key)
.map_err(|e| e.to_string())?;
entry.get_password()
.map_err(|e| e.to_string())
}HTTPS 和证书
rust
// 强制 HTTPS
#[command]
async fn secure_fetch(url: String) -> Result<String, String> {
if !url.starts_with("https://") {
return Err("Only HTTPS URLs are allowed".to_string());
}
let client = reqwest::Client::builder()
.min_tls_version(reqwest::tls::Version::TLS_1_2)
.build()
.map_err(|e| e.to_string())?;
client.get(&url)
.send()
.await
.map_err(|e| e.to_string())?
.text()
.await
.map_err(|e| e.to_string())
}更新安全
json
// tauri.conf.json
{
"plugins": {
"updater": {
"endpoints": [
"https://releases.myapp.com/{{target}}/{{arch}}/{{current_version}}"
],
"pubkey": "YOUR_PUBLIC_KEY_HERE"
}
}
}rust
// 签名验证
// 1. 生成密钥对
// tauri signer generate -w ~/.tauri/myapp.key
// 2. 构建时签名
// TAURI_SIGNING_PRIVATE_KEY=~/.tauri/myapp.key tauri build安全最佳实践
rust
// 1. 最小权限原则
// 只请求必需的权限
// 2. 避免 eval 和动态代码
// CSP 禁用 unsafe-eval
// 3. 敏感操作双重验证
#[command]
async fn delete_account(
password: String,
state: State<'_, AuthState>,
) -> Result<(), String> {
// 二次验证密码
state.verify_password(&password)?;
// 执行删除
Ok(())
}
// 4. 日志脱敏
fn log_request(url: &str, headers: &Headers) {
let sanitized_url = url.replace(|c: char| !c.is_ascii(), "*");
log::info!("Request to: {}", sanitized_url);
// 不记录敏感 header
}面试要点
- 权限系统:Capabilities → Permissions → Scopes 三层模型
- CSP 配置:限制脚本来源、网络请求
- IPC 安全:输入验证、权限检查
- 安全存储:系统密钥链、加密存储