插件系统与权限模型
本教程共 48 篇 · 第 31 篇 · 更新于 2026-08-09 · 约 8 分钟阅读
本节目标:读完你能说清一个官方插件在前后端分别叫什么名字、怎么接进项目,以及 2.x 的 capabilities / permissions 是怎么给前端「发许可证」的。
Tauri 把很多功能做成了插件(plugin):对话框、文件系统、系统托盘、HTTP 请求、通知……它们都是可插拔的模块,要用哪个装哪个,不用的就不进包里,应用体积小、攻击面也小。本章先讲插件怎么「接进来」,再重点讲 2.x 的权限模型(permission model)——这是 2.x 和 1.x 差别最大、也最容易让新手踩坑的地方。
前端包与 Rust 端的一一对应
每个官方插件都有「两面」:一面跑在 Rust 核心进程(真正干活),一面跑在前端(给你写 JS 时调用)。它们的名字是成对出现的,规律非常固定:
- 前端 JS 包:
@tauri-apps/plugin-<名字> - Rust 端 crate:
tauri-plugin-<名字>
举几个你后面会遇到的例子:
| 功能 | 前端包 | Rust 端 crate |
|---|---|---|
| 对话框 | @tauri-apps/plugin-dialog | tauri-plugin-dialog |
| 文件系统 | @tauri-apps/plugin-fs | tauri-plugin-fs |
| 系统通知 | @tauri-apps/plugin-notification | tauri-plugin-notification |
| HTTP 请求 | @tauri-apps/plugin-http | tauri-plugin-http |
| 全局快捷键 | @tauri-apps/plugin-global-shortcut | tauri-plugin-global-shortcut |
记住这个对应关系是关键:你在前端 import 的包名,永远对应 Rust 端那个同名 crate 的 .init()。两端缺一个,功能都跑不起来。
一个插件怎么接进项目
接一个插件要动三处:Rust 依赖、Rust 初始化、前端依赖。以对话框插件为例,完整流程是:
第一步,在 src-tauri 目录把 Rust 端加进 Cargo.toml:
cd src-tauri
cargo add tauri-plugin-dialog
第二步,在 lib.rs 里用 .plugin(...) 把插件初始化挂到应用上:
// src-tauri/src/lib.rs
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_dialog::init()) // Rust 端初始化
.run(tauri::generate_context!())
.expect("运行 Tauri 应用时出错");
}
第三步,前端装对应的 JS 包:
npm install @tauri-apps/plugin-dialog
三步走完,你就能在前端 import { open } from "@tauri-apps/plugin-dialog" 去调它了。文件系统和通知等其它插件,只是把名字换一下,流程一模一样。
Tip官方脚手架提供了一条捷径:在
src-tauri目录运行cargo tauri add dialog(把dialog换成插件名),它会一次性帮你加 Rust 依赖、初始化插件、装好前端包,还能顺手把默认权限写进 capability。新手用它最省心。
2.x 的权限模型:capabilities + permissions
2.x 有个核心规矩:前端默认什么系统能力都调不了,必须显式授权。你装了插件、调了 .init(),前端也 import 成功了,但如果没在 capability 里给这个插件「发许可证」,运行时一调用就会被安全层拦下,报类似 dialog.open not allowed 的错误。
这套机制由两层组成:
- permission(权限):描述「某个命令能不能被调」。每个插件自带一组 permission,比如
dialog:allow-open表示「允许调打开对话框这个命令」。标识符格式是<插件名>:<命令>,例如fs:allow-read-file。 - capability(能力):把若干 permission 打包,并指定「授予哪些窗口 / WebView」。它是一份 JSON 文件,放在
src-tauri/capabilities/目录里。
换句话说,permission 是「单张许可证」,capability 是「把许可证发给谁的一张派单」。前端调用是否被放行,由它所在的窗口在不在某个 capability 的 windows 列表里、且那个 capability 含不含对应 permission 来决定。
兼容说明:1.x 默认放开 vs 2.x 最小权限
Note1.x 与 2.x 权限模型的区别(仅本节说明,正文仍以 2.x 为准)
- Tauri 1.x:在
tauri.conf.json里用allowlist配置。默认情况下,只要你装了插件,开发环境里前端基本就能直接调,没有细到「哪个窗口、哪个命令」的显式授权概念,权限相对宽松。- Tauri 2.x:彻底改为 capabilities + permissions 模型。所有插件命令默认全部拒绝,必须由你在
capabilities/*.json里显式列出要授权的permissions,并指定作用到哪些windows。即便在开发模式,没授权也会被拦。每个窗口拿到的权限是「刚好够用」的最小集合。一句话:1.x 是「默认放开、按需收紧」;2.x 是「默认全关、按需显式授权」。这也是 2.x 从 1.x 升级后,原本能跑的
invoke突然报not allowed的最常见原因——不是代码错了,是没补 capability。
capabilities 文件怎么写
一个典型的 capability 文件长这样,放在 src-tauri/capabilities/default.json:
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "main-capability",
"description": "主窗口的能力与权限",
"windows": ["main"],
"permissions": [
"core:default",
"dialog:default",
"fs:default"
]
}
逐字段说明:
$schema:让编辑器能对权限名做自动补全,照抄即可。identifier:这份 capability 的名字,全局唯一。windows:这份权限授予哪些窗口,写窗口的 label(注意是 label,不是标题)。["main"]表示只给主窗口;写["*"]则给所有窗口。permissions:真正授权的权限列表。core:default是核心能力的基础包;dialog:default、fs:default是各插件的「默认权限全家桶」,一次性开放该插件常用命令。
多数插件都提供一个 :default 集合,开它就够用;想要更克制,就把 :default 换成具体的 :allow-xxx(见第 32 章 fs 示例)。
Warningcapability 是按窗口 label 生效的,不是按窗口标题。很多新手把窗口标题写进
windows却怎么都不生效,问题就出在这里。窗口的 label 是在tauri.conf.json或创建窗口时定的那个字符串。
permissions 还能带 scope
有些权限不只是「能不能调」,还管「能访问哪」。文件系统就是典型:你允许读文件,但不能让前端读你整个硬盘。于是 permission 之外还有一层 scope(作用域),用来圈定允许访问的具体路径。
scope 写在 permission 的对象形式里,例如(细节第 32 章展开):
{
"identifier": "fs:allow-read-file",
"allow": [{ "path": "$APPDATA/**" }]
}
这表示「允许调读文件命令,但只允许读 $APPDATA 及其子目录下的内容」。这种「命令权限 + 路径作用域」的组合,才是 2.x 安全模型的精细之处。deny 优先级高于 allow,圈进 deny 的路径即使 allow 了也会被挡。
小结
Tauri 插件分两面:前端 @tauri-apps/plugin-<名> 对应 Rust 端 tauri-plugin-<名>,接项目要在 Rust 加依赖、.plugin(...init())、前端装 JS 包三处动手。2.x 的安全核心是 capabilities + permissions:默认全拒绝,必须显式授权——permissions 是单张许可证(如 dialog:allow-open),capabilities 是把许可证按窗口派发的清单。和 1.x 的 allowlist 默认宽松不同,2.x 改成最小权限、按需显式开放,这也是升级后命令突然 not allowed 的根源。部分权限(如 fs)还能叠加 scope 进一步限制可访问的路径。