权限与能力最佳实践
本教程共 48 篇 · 第 41 篇 · 更新于 2026-08-09 · 约 10 分钟阅读
本节目标:读完你能写出规范的 capabilities 配置文件,理解 permission 的粒度层次,会用 scope 把权限锁到最小作用域,并掌握按需授权的实战原则。
前面几章我们一直在用各种插件,每次都要在 capabilities/default.json 里加权限。但一直没有系统讲过这套权限体系怎么设计、怎么配、怎么配才安全。这一章把 permissions、capabilities 和 scope 三者串起来讲清楚,给你一套可落地的最佳实践。
三个核心概念的关系
先理清三个容易混的概念:
- Permission(权限):描述「能不能用某个命令」。比如
fs:allow-read-file是一个权限,允许前端调用读文件命令。 - Scope(作用域):描述「能用这个命令操作哪些资源」。比如有了
fs:allow-read-file还不够,还得限定只能读$APPDATA目录,这个路径限制就是 scope。 - Capability(能力):把 permission 和 scope 打包,绑定到具体的窗口上。一个 capability 说的是「main 窗口有这些权限、这些 scope」。
打个比方:permission 是门禁卡上的一个权限项(能进大门),scope 是这个权限项的限制(只能进 3 楼),capability 是整张门禁卡(张三的卡,有这些权限项和限制,适用于这栋楼)。
capabilities 文件结构
Capability 文件放在 src-tauri/capabilities/ 目录下,JSON 格式。每个文件描述一组权限绑定到哪些窗口:
// src-tauri/capabilities/default.json
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "main-capability",
"description": "主窗口的权限配置",
"windows": ["main"],
"permissions": [
"core:default",
"core:window:allow-set-title",
"fs:default",
{
"identifier": "fs:allow-read-file",
"allow": [{ "path": "$APPDATA/**" }]
},
{
"identifier": "fs:allow-write-file",
"allow": [{ "path": "$APPDATA/**" }]
}
]
}
逐字段解释:
$schema:指向 Tauri 自动生成的 JSON Schema 文件,让 IDE 有自动补全。桌面应用用desktop-schema.json,移动端用mobile-schema.json。identifier:这个 capability 的唯一标识,随便取名,不重复就行。windows:哪些窗口应用这套权限。["main"]表示只给 label 为main的窗口。["*"]表示所有窗口。permissions:权限列表,可以是字符串(引用预定义权限)或对象(权限 + scope)。
Tip
src-tauri/capabilities/目录下的所有.json文件默认自动启用。如果你在tauri.conf.json的app.security.capabilities里显式指定了 capability 列表,则只有指定的生效。
permission 的粒度层次
Tauri 的权限不是非黑即白的「全开或全关」,而是有层次:
第一层:default 权限集
每个插件都有一个 default 权限集,包含一组「开箱即用」的基础权限。比如 fs:default 包含了所有读相关命令和 $APP 目录的 scope。这是最省事的用法,但安全性也最低——它开放了一整类操作。
"fs:default"
第二层:单命令权限
如果你只需要某个具体命令,不要用 default 全开,而是只授权那一个:
"fs:allow-write-text-file"
这比 fs:default 安全得多——前端只能写文件,不能读、不能删、不能监控。
第三层:权限 + scope
最精细的控制:只开一个命令,且限定操作作用域:
{
"identifier": "fs:allow-write-text-file",
"allow": [{ "path": "$HOME/test.txt" }]
}
这表示前端只能写 $HOME/test.txt 这一个文件,其他路径全拒绝。
Note权限粒度从粗到细:
plugin:default→plugin:allow-<command>→plugin:allow-<command>+ scope。安全要求越高,粒度越细。实际开发中,用 default 配合 scope 是最常见的折中方案——既不用列一堆单命令权限,又把可操作作用域锁住了。
scope:路径和资源的精细控制
Scope 是 Tauri 2 权限体系里最有价值的特性之一。它让权限不再是「有/没有」的二元选择,而是「有,但只能碰这些」。
Scope 分 allow 和 deny 两个列表:
allow:允许访问的路径/资源deny:明确禁止的路径/资源
deny 的优先级高于 allow。即使某个路径匹配了 allow,只要也进了 deny,运行时照样被挡。
{
"identifier": "fs:allow-read-file",
"allow": [{ "path": "$APPLOCALDATA/**" }],
"deny": [{ "path": "$APPLOCALDATA/EBWebView/**" }]
}
这个配置表示:允许读 $APPLOCALDATA 下的所有文件,但排除 EBWebView 子目录——因为那里存着 WebView 的敏感数据。这种「允许大部分、排除小部分」的模式在实际中非常常用。
Scope 中的路径支持 glob 通配符:
*:匹配单层路径**:递归匹配所有子目录
$APPDATA/* → 只匹配 $APPDATA 下的直接子文件/子目录
$APPDATA/** → 递归匹配 $APPDATA 下所有层级的文件和目录
$APPDATA/config/* → 匹配 $APPDATA/config/ 下的直接子文件
按窗口分权
不同窗口需要不同权限。比如主窗口需要文件读写,一个只读的预览窗口不需要写权限。你可以为不同窗口创建不同的 capability 文件:
// src-tauri/capabilities/main.json
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "main-capability",
"windows": ["main"],
"permissions": [
"core:default",
"fs:default",
{
"identifier": "fs:allow-write-file",
"allow": [{ "path": "$APPDATA/**" }]
}
]
}
// src-tauri/capabilities/preview.json
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "preview-capability",
"windows": ["preview"],
"permissions": [
"core:default",
{
"identifier": "fs:allow-read-file",
"allow": [{ "path": "$APPDATA/**" }]
}
]
}
主窗口能读写,预览窗口只能读。即使预览窗口被 XSS 攻击,攻击者也无法修改文件——因为它的权限集里根本没有写权限。
Warning如果一个窗口同时被多个 capability 文件引用,它的有效权限是这些 capability 的并集,不是交集。比如窗口
main出现在两个 capability 里,一个给了读权限一个给了写权限,那 main 窗口同时有读写权限。务必检查每个窗口最终合并后的权限集,避免意外叠加。
按平台分权
有些插件只在特定平台有意义。比如 nfc:allow-scan 在桌面平台没意义,global-shortcut 在移动端没意义。Capability 支持 platforms 字段做平台过滤:
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "desktop-capability",
"windows": ["main"],
"platforms": ["linux", "macOS", "windows"],
"permissions": ["global-shortcut:allow-register"]
}
{
"$schema": "../gen/schemas/mobile-schema.json",
"identifier": "mobile-capability",
"windows": ["main"],
"platforms": ["iOS", "android"],
"permissions": ["nfc:allow-scan", "biometric:allow-authenticate"]
}
这样桌面权限和移动权限互不干扰,也不会在错误平台上意外启用。
自定义权限集(Permission Set)
如果你发现自己反复写同一组权限,可以定义一个 permission set,把多个权限打包成一个新的标识符。在 src-tauri/permissions/ 目录下创建 TOML 文件:
# src-tauri/permissions/home-read-extends.toml
[[set]]
identifier = "allow-home-read-extended"
description = "允许在 $HOME 目录下非递归读取文件和创建目录"
permissions = [
"fs:read-files",
"fs:scope-home",
"fs:allow-mkdir"
]
然后在 capability 里直接引用这个 set:
{
"permissions": [
"core:default",
"allow-home-read-extended"
]
}
注意自定义权限集的 identifier 没有 fs: 前缀——因为它属于应用本身,不是插件的。
Note权限文件(permissions)只能用 TOML 格式,capability 文件可以用 JSON 或 TOML。这是 Tauri 的硬性规定。
最小授权原则清单
把前面所有最佳实践浓缩成一份可执行的清单:
- 不用
*通配权限:每个权限都要明确写出来,不要图省事用通配符放开所有命令。 - 优先用单命令权限:只用到一个命令就只开那一个,不要用
default全开。 - 必须配 scope:授权文件操作类权限时,必须用
allow限定可访问路径,不能让前端碰整个硬盘。 - 善用 deny:敏感子目录用
deny显式排除,即使它被allow覆盖。 - 按窗口分权:不同窗口用不同 capability 文件,权限最小化。
- 检查并集:一个窗口出现在多个 capability 里时,合并后的权限可能比你预期的大,要逐一核查。
- 按平台过滤:桌面和移动端的权限用
platforms分开,避免无意义的权限被启用。 - 定期审计:随着应用迭代,权限会只增不减。定期过一遍 capabilities 配置,删掉不再需要的权限。
TipTauri 会自动生成 JSON Schema 文件(在
src-tauri/gen/schemas/目录下),其中包含所有可用的权限列表。在 capability 文件里设好$schema后,IDE 会自动补全可用的权限标识符,非常方便。不确定某个插件有哪些权限时,去看这个 schema 文件。
小结
Tauri 2 的权限体系由三层组成:permission(单条权限)、scope(作用域限制)、capability(权限与窗口的绑定)。写 capability 时,优先用单命令权限而非 default 全开,必须给文件操作类权限配 scope 限定路径,善用 deny 排除敏感子目录。多窗口应用要按窗口分权,不同平台用 platforms 过滤。反复使用的权限组合可以定义成 permission set 复用。核心原则只有一条:默认全拒绝,按需最小授权——每多开一个权限,都要问自己「这个权限真的需要吗?能不开就不开」。