3 编写插件权限

原文链接: https://tauri.app/learn/security/writing-plugin-permissions/

本练习的目标是更好地理解在编写自己的插件时如何创建插件权限。

完成后,你将有能力为自己的插件创建简单的权限。 你还会得到一个示例 Tauri 插件,其中的权限部分是自动生成的,部分是手工编写的。

1. 创建一个 Tauri 插件

在我们的示例中,我们将借助 Tauri 的 cli 来引导生成 Tauri 插件源代码结构。 请确保已安装所有前置条件,并通过运行 cargo tauri info 确认你的 Tauri CLI 版本正确。

输出应当显示 tauri-cli 版本为 2.x。 本分步说明中我们使用 pnpm,但你可以选择其它包管理器并在命令中相应替换。

安装好较新的版本后,你就可以使用 Tauri CLI 创建插件了。

1
2
3
4
5
6
cd tauri-learning
cargo tauri plugin new test
cd tauri-plugin-test
pnpm install
pnpm build
cargo build

2. 创建一个新命令

为了演示一些实用而简单的东西,假设我们的命令把用户输入写入临时文件夹中的文件,同时给文件加上一些自定义头信息。

我们把命令命名为 write_custom_file,在 src/commands.rs 中实现它,并把它加入插件 builder 以暴露给前端。

Tauri 的核心工具会为这个命令自动生成 allow 和 deny 权限,因此我们不必操心这一点。

命令实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
use tauri::{AppHandle, command, Runtime, Manager};

use crate::models::*;
use crate::Result;
use crate::TestExt;

#[command]
pub(crate) async fn ping<R: Runtime>(
    app: AppHandle<R>,
    payload: PingRequest,
) -> Result<PingResponse> {
    app.test1().ping(payload)
}

#[command]
pub(crate) async fn write_custom_file<R: Runtime>(
    user_input: String,
    app: AppHandle<R>,
) -> Result<String> {
    std::fs::write(app.path().temp_dir().unwrap(), user_input)?;
    Ok("success".to_string())
}

为你新增的命令自动生成内置权限:

1
const COMMANDS: &[&str] = &["ping", "write_custom_file"];

这些内置权限会由 Tauri 构建系统自动生成,并出现在 permissions/autogenerated/commands 文件夹中。 默认会创建一个 enable-<command> 和一个 deny-<command> 权限。

3. 暴露新命令

上一步是编写实际的命令实现。 接下来我们要把它暴露给前端以便使用。

配置 Tauri builder 生成 invoke handler,把前端的 IPC 请求转给刚实现的命令:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("test")
    .invoke_handler(tauri::generate_handler![
        commands::ping,
        commands::write_custom_file,
    ])
    .setup(|app, api| {
        #[cfg(mobile)]
        let test = mobile::init(app, api)?;
        #[cfg(desktop)]
        let test = desktop::init(app, api)?;
        app.manage(test);

        // 托管 state,使命令可以访问它
        app.manage(MyState::default());
        Ok(())
    })
    .build()
}

在前端模块中暴露新命令。

这一步对示例应用成功导入前端模块是必需的。这只是为了方便,没有安全影响,因为命令处理器已经生成,命令也可以从前端手动调用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import { invoke } from '@tauri-apps/api/core'

export async function ping(value: string): Promise<string | null> {
  return await invoke<{value?: string}>('plugin:test|ping', {
    payload: {
      value,
    },
  }).then((r) => (r.value ? r.value : null));
}

export async function writeCustomFile(user_input: string): Promise<string> {
 return await invoke('plugin:test|write_custom_file',{userInput: user_input});
}

确保你的包已构建:

pnpm build

4. 定义默认插件权限

由于我们的插件应当默认暴露 write_custom_file 命令,我们应当把它加入 default.toml 权限。

把这个加入我们的默认权限集,以允许我们刚暴露的新命令。

1
2
3
[default]
description = "Default permissions for the plugin"
permissions = ["allow-ping", "allow-write-custom-file"]

5. 从示例应用调用测试命令

生成的插件目录结构包含一个 examples/tauri-app 文件夹,其中有一个可直接使用的 Tauri 应用来测试该插件。

由于我们新增了命令,需要稍微修改前端,改为调用我们的新命令。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
  import Greet from './lib/Greet.svelte'
  import { ping, writeCustomFile } from 'tauri-plugin-test-api'

  let response = ''

  function updateResponse(returnValue) {
    response += `[${new Date().toLocaleTimeString()}]` + (typeof returnValue === 'string' ? returnValue : JSON.stringify(returnValue)) + '<br>'
  }

  function _ping() {
    ping("Pong!").then(updateResponse).catch(updateResponse)
  }
  function _writeCustomFile() {
    writeCustomFile("HELLO FROM TAURI PLUGIN").then(updateResponse).catch(updateResponse)
  }
</script>

<main class="container">
  <h1>Welcome to Tauri!</h1>

  <div class="row">
    <a href="https://vitejs.dev" target="_blank">
      <img src="/vite.svg" class="logo vite" alt="Vite Logo" />
    </a>
    <a href="https://tauri.app" target="_blank">
      <img src="/tauri.svg" class="logo tauri" alt="Tauri Logo" />
    </a>
    <a href="https://svelte.dev" target="_blank">
      <img src="/svelte.svg" class="logo svelte" alt="Svelte Logo" />
    </a>
  </div>

  <p>
    Click on the Tauri, Vite, and Svelte logos to learn more.
  </p>

  <div class="row">
    <Greet />
  </div>

  <div>
    <button on:click="{_ping}">Ping</button>
    <div>{@html response}</div>
  </div>
  <div>
    <button on:click="{_writeCustomFile}">Write</button>
    <div>{@html response}</div>
  </div>


</main>

<style>
  .logo.vite:hover {
    filter: drop-shadow(0 0 2em #747bff);
  }

  .logo.svelte:hover {
    filter: drop-shadow(0 0 2em #ff3e00);
  }
</style>

运行它并点击 “Write” 按钮,你应该会看到:

success

并且你会在临时文件夹中找到一个 test.txt 文件,其中包含来自我们新实现的插件命令的消息。 🥳

最后修改 September 25, 2026: 更新 (4c0ee2db0)