技术分享

Tauri 项目实践:客户端与 Web 端的授权登录实现方案

在跨平台应用开发中(如基于 Tauri 构建的 Mind Elixir 客户端),如何让应用从 Web 端顺畅地获取授权并完成登录往往是一个常见且重要的需求。本文将总结我们在这个 Tauri 项目中探索的两种登录实现方法,并分享一个在 macOS 上开发时遇到的非常经典的坑点。

发布于 2026年3月6日
5 分钟阅读
Mind Elixir Team
Tauri自定义 scheme

在跨平台应用开发中(如基于 Tauri 构建的 Mind Elixir 客户端),如何让应用从 Web 端顺畅地获取授权并完成登录往往是一个常见且重要的需求。本文将总结我们在这个 Tauri 项目中探索的两种登录实现方法,并分享一个在 macOS 上开发时遇到的非常经典的坑点。

旧的登录方式:本地 HTTP Server 通信(遗留方案)

在项目最初,为了解决 Web 端把 Token 传回桌面端的痛点,我们采取了在本地启动 HTTP 服务器进行跨应用通信的方法:

实现原理

通过 Tauri 结合 Rust 的 axum 框架,桌面程序会在后台启动一个微型的本地服务器,监听特定端口(如 127.0.0.1:6595)。当用户在浏览器(如 cloud.mind-elixir.com)中登录完毕后,Web 页面直接向这个本地接口发出带上登录参数的 POST 请求:

// axum_router.rs
async fn login_handler(
    headers: HeaderMap,
    Query(params): Query<Params>,
    handle_clone: tauri::AppHandle,
) -> impl IntoResponse {
    let token = params.token;
    // 收到 HTTP 请求后,向 Tauri 的前端触发全局 login 事件
    let _ = handle_clone.emit("login", Login { token: token });

    // ...处理 CORS 返回
}

接下来,React 前端监听这个全局事件,获取 Token 存入本地存储后即可完成登录:

// App.tsx
const unlisten = listen<{ token: string }>('login', async (e) => {
  localStorage.setItem('token', e.payload.token)
  await fetchData()
  toast.success('登录成功')
})

优缺点

  • 优点:实现逻辑简单粗暴,并且非常方便在开发环境(tauri dev)下随意调试,无需进行系统级的协议注册。
  • 缺点:这是一个相对较重的方案;需要额外占用用户电脑端口,偶发情况还可能受制于严格的浏览器跨域(CORS)策略或端口被占用从而导致通信失败;另外,该方案在移动端无法唤起应用窗口。

新的登录方式:自定义 Scheme / Deep Link

由于本地服务器面临上述潜在风险,并且不符合系统深层集成的新趋势,我们后续改用了更加优雅和原生的方案——自定义 Scheme 登录(如唤起 mind-elixir://)

配置与实现

  1. 引入插件和配置: 启用 @tauri-apps/plugin-deep-link 插件,并在 tauri.conf.json 下注册我们的特定协议头部 mind-elixir

    "plugins": {
      "deep-link": {
        // 移动端(iOS/Android)配置
        "mobile": [
          {
            "scheme": ["mind-elixir"],
            "appLink": false
          }
        ],
        // 桌面端配置
        "desktop": {
          "schemes": ["mind-elixir"]
        }
      }
    }
    

    注:移动端配置生效同时依赖同步写入 Android 的 AndroidManifest.xml (<data android:scheme="mind-elixir" />) 与 iOS 的 Info.plist (CFBundleURLSchemes) 中。

  2. 桌面系统唤醒支持: 在 src-tauri/src/lib.rs 的初始化钩子处,我们需要给 Windows 和 Linux 用户调用显式的注册 API。

    #[cfg(any(windows, target_os = "linux"))]
    {
        use tauri_plugin_deep_link::DeepLinkExt;
        app.deep_link().register_all()?;
    }
    
  3. 前端接收请求: 在收到协议请求(即网页重定向到了形如 mind-elixir://login?token=xxxx 的长链接)时,使用 Tauri 的 API 解析深层链接并完成授权。既要负责冷启动阶段获取(getCurrent()),也要监控运行时唤醒(onOpenUrl):

    import { onOpenUrl, getCurrent } from '@tauri-apps/plugin-deep-link'
    import { getCurrentWindow } from '@tauri-apps/api/window'
    import { isMobile } from './utils/platform' // 项目中自定义的环境判断工具
    
    const handleDeepLinkUrls = async (urls: string[]) => {
      if (!urls || urls.length === 0) return
    
      // 【各端表现差异处理】
      // 移动端(尤其是 iOS/Android)点击 Deep Link 浏览器会自动切换/唤醒对应的 App 到前台。
      // 但在桌面端接收到 deep link 事件后,应用窗口可能依然保持在后台,因此我们需要通过 window API 手动将其调出并聚焦。
      if (!isMobile()) {
        const win = getCurrentWindow()
        await win.show()
        await win.setFocus()
      }
    
      // 检查数组中以特定协议开头的链接
      const loginUrl = urls.find((url) => url.startsWith('mind-elixir://login'))
      if (loginUrl) {
        const url = new URL(loginUrl)
        const token = url.searchParams.get('token')
        if (token) {
          localStorage.setItem('token', token)
          await fetchData()
          toast.success('登录成功')
        }
      }
    }
    
    // 处理冷启动
    getCurrent().then((urls) => {
      if (urls) handleDeepLinkUrls(urls)
    })
    // 处理运行时被协议唤醒
    onOpenUrl(handleDeepLinkUrls)
    

利用这种方式,用户在使用浏览器验证登陆态后,浏览器能顺滑提示是否打开目标应用,体验极佳。

避坑指南:macOS/Linux 的自定义 Scheme 调试限制

根据 Tauri 官方文档说明,在 macOS 和 Linux 系统下,Deep Link(自定义 Scheme)在开发模式(tauri dev)下是无法正常工作的。

如果你修改了基于 Scheme 登录的代码,请务必将其打包后(tauri build)运行该程序来进行测试。这同时也体现了第一种 HTTP 通信方案的一个优势——它可以在不用频繁打包的开发阶段充当最佳的调试通道。