最近给一个带屏幕截图、录屏、辅助功能和全局快捷键的 Tauri macOS 应用做本机测试时,遇到了一个很烦的问题:每次重新编译后,系统都像看到了一个新应用,需要重新到“系统设置 → 隐私与安全性”里授权屏幕录制。
真正的问题不是 TCC 本身“随机失忆”,而是开发构建一直在使用 ad-hoc 签名。每次构建产生的代码身份不稳定,macOS 就可能把它当作另一个应用。
解决办法是:使用免费的 Apple 账户登录 Xcode,创建 Personal Team 的 Apple Development 证书,然后让本机测试包始终使用同一个签名身份。
先说结论
本机开发测试可以使用免费 Apple 账户的 Apple Development 证书,不需要购买 Apple Developer Program。
它适合:
- 本机运行和调试 macOS 应用;
- 保持同一个应用代码身份;
- 减少屏幕录制、麦克风、辅助功能等 TCC 权限的重复授权。
它不适合:
- Developer ID 分发;
- 公证(Notarization);
- 面向其他用户的正式安装包发布。
发布和本机开发应该分开:本机使用 Apple Development,CI/Release 继续使用项目原来的 ad-hoc 签名策略。
1. 在 Xcode 中登录免费 Apple 账户
打开 Xcode,进入:
1 | Xcode → Settings → Apple Accounts |
点击 Add Apple Account...,登录 Apple 账户。登录成功后,账户下应该能看到:
1 | Personal Team |
进入 Personal Team,点击 Manage Certificates...,通过 Create Certificate 创建 Apple Development 证书。
免费账户的 Personal Team 足够用于本机测试,但不会提供 Developer ID Application 证书,也不会让你绕过公证要求。
2. 检查代码签名身份
用下面的命令检查本机是否已经有可用签名身份:
1 | security find-identity -v -p codesigning |
正常情况下应该看到类似输出:
1 | 1) ABCDEF0123456789ABCDEF0123456789ABCDEF01 |
注意这里的 Apple Development 才是我们想要的开发签名。下面这些都不是同一回事:
Developer ID Application:付费开发者账号用于外部分发;Apple Distribution:分发或提交 App Store;-:ad-hoc 签名,免费但代码身份不适合解决 TCC 反复授权。
3. 处理 WWDR G3 证书链
这一步是最容易被忽略的地方。
某些 macOS 环境里,钥匙串中只有已经过期的旧版 WWDR 中间证书。此时 Xcode 可能已经创建了 Apple Development 证书,但命令行签名会报:
1 | Warning: unable to build chain to self-signed root |
Apple Development 证书现在通常由 Worldwide Developer Relations - G3 中间证书签发。可以从 Apple 官方 PKI 页面下载:
命令行安装方式如下:
1 | tmp_dir="$(mktemp -d /tmp/apple-wwdr-g3.XXXXXX)" |
然后再次检查:
1 | security find-identity -v -p codesigning |
如果从 0 valid identities found 变成出现 Apple Development 身份,说明证书链已经恢复。
4. Tauri 应用的签名思路
Tauri 的 macOS 应用不是只有一个可执行文件。典型 bundle 结构大致如下:
1 | Qx.app/ |
主程序和嵌套的 helper、外部二进制都需要保持可验证的签名。对于 Tauri bundle,本机测试可以使用:
1 | codesign \ |
这里几个参数的作用是:
| 参数 | 作用 |
|---|---|
--force |
替换 bundle 中已有的 ad-hoc 或旧签名 |
--deep |
递归处理 bundle 内的嵌套代码 |
--sign |
指定 Apple Development 签名身份 |
--timestamp=none |
本机开发签名不依赖分发时间戳 |
--identifier |
固定代码签名标识,通常与 Bundle ID 一致 |
--entitlements |
使用应用声明的 macOS 权限配置 |
--deep 适合这里的 Tauri 本机 bundle 快速签名。正式分发时,应该根据嵌套代码的层级逐个签名,再签最外层 bundle,并使用 Developer ID 和公证流程。
5. 一个可复用的本机签名脚本
下面是这次本机测试实际采用的脚本模板。把它保存为项目外的本地脚本,或者放到不提交的个人工具目录中,避免把本机证书身份写进公共配置。
1 |
|
使用时:
1 | export APPLE_DEVELOPMENT_IDENTITY='Apple Development: [email protected] (TEAMID1234)' |
以 Qx 为例,本机实际签名命令类似下面这样:
1 | identity='Apple Development: [email protected] (TEAMID1234)' |
Qx bundle 内的 qx-ffmpeg 也会被递归处理。签名后可以看到类似结果:
1 | Authority=Apple Development: ... |
6. xcodeutil 不是关键
这次没有依赖名为 xcodeutil 的系统命令。macOS/Xcode 的标准工具链是:
security:查看钥匙串、证书和签名身份;codesign:对 Mach-O 文件和.appbundle 签名;xcodebuild:构建 Xcode 工程;spctl:检查 Gatekeeper 评估结果。
如果某个第三方工具叫 xcodeutil,只要它最终调用的是同一个 Apple Development 身份,效果可以等价;但工具名字本身不会让 TCC 授权稳定,真正起作用的是最终的代码签名身份和 Bundle ID。
如果目标应用安装在 /Applications,也可以对安装后的 bundle 再签一次:
1 | ./sign-macos-dev.sh /Applications/MyApp.app |
签名之前最好先退出正在运行的应用,避免进程仍然使用旧 bundle 内容。
7. 验证签名是否真的生效
验证 bundle 完整性
1 | codesign --verify --deep --strict --verbose=2 /Applications/MyApp.app |
看到下面的结果就说明 bundle 结构完整:
1 | MyApp.app: valid on disk |
查看签名身份
1 | codesign -dvvv /Applications/MyApp.app 2>&1 |
重点确认:
1 | Identifier=com.example.myapp |
其中最重要的是:
Authority不再是 ad-hoc;TeamIdentifier已出现;Identifier与 Bundle ID 稳定一致。
查看实际 entitlements
1 | codesign -d --entitlements :- /Applications/MyApp.app |
不要只看源码中的 Entitlements.plist,最终以签名后的 bundle 输出为准。
8. 为什么这样可以减少 TCC 重复授权
macOS 的 TCC 不只是按照应用文件名判断身份。它会结合应用的代码签名要求、签名标识和相关代码身份来判断“这是不是之前授权过的那个应用”。
如果每次构建都使用 ad-hoc 签名,代码身份可能随构建变化:
1 | 旧构建:ad-hoc identity A |
即使 Bundle ID 没变,TCC 也可能把新版本当作新应用,于是重新要求屏幕录制或辅助功能授权。
固定使用同一 Apple Development 证书后,身份变成:
1 | 旧构建:Apple Development + Team ID + Bundle ID |
这会显著提高授权记录的复用概率。不过首次从 ad-hoc 切换到 Apple Development 时,仍然建议重新授权一次;这是代码身份改变后的正常行为。
还需要注意,改变下面这些内容也可能导致 macOS 重新评估授权:
- Bundle ID;
- 签名证书或 Team ID;
- 关键 entitlements;
- 主程序实际路径或应用结构。
9. Tauri 项目中发布和本机测试要分开
本机使用 Personal Team 签名,不代表 GitHub Release 也应该使用 Personal Team。
例如项目的 Release Action 仍然可以独立执行:
1 | codesign \ |
CI 机器上没有你的 Apple Development 私钥,也不应该把个人证书提交到仓库或 GitHub Secrets。这样做的好处是:
- 本机开发签名可以稳定 TCC 身份;
- Release Action 仍然保持原来的免费 ad-hoc 流程;
- 不会把 Apple 账户、私钥或 provisioning profile 带进仓库;
- CI 不依赖某一台开发机的钥匙串状态。
唯一要记住的是:如果 CI 产物下载到本机后运行,它使用的是另一个签名身份,可能仍然需要单独授权。这是预期行为,不是本机开发签名失效。
10. 常见错误排查
0 valid identities found
通常表示:
- Xcode 没有登录 Apple 账户;
- Personal Team 没有创建 Apple Development 证书;
- 证书的私钥不在当前用户钥匙串;
- WWDR 中间证书缺失或过期。
先执行:
1 | security find-identity -v -p codesigning |
再检查 Xcode 的 Apple Accounts 页面和 WWDR G3 中间证书。
errSecInternalComponent
这个错误信息很笼统,但在开发签名场景里经常与证书链有关。重点看签名时是否同时出现:
1 | unable to build chain to self-signed root |
如果出现,优先补齐与证书签发者匹配的 WWDR 中间证书。不要盲目删除所有钥匙串证书。
签名成功但 TCC 仍然重新询问
确认以下几点:
- 你运行的确实是刚签名的 bundle;
- 没有被旧的 ad-hoc 构建覆盖;
- Bundle ID 没有变化;
- 应用已完全退出后重新启动;
- 第一次从 ad-hoc 切换到 Apple Development 后,已经在系统设置中重新授权。
可以用下面的命令确认实际运行文件:
1 | ps aux | grep '[M]yApp.app' |
spctl 拒绝本机开发包
Apple Development 签名主要用于开发测试,不等于 Developer ID 分发签名。不要把本机开发包的 spctl 结果和正式分发包的 Gatekeeper 结果混为一谈。
正式分发需要 Developer ID Application、Hardened Runtime、Notarization 和 stapling;这些不属于免费 Personal Team 的能力范围。
总结
这次问题的关键不是给应用“多加几个 entitlement”,而是让 macOS 每次看到的代码身份保持稳定:
1 | 固定 Apple Development 证书 |
对于 Tauri 项目,最实用的分工是:本机开发包使用 Apple Development,Release Action 继续使用独立的 CI 签名流程。这样既能改善本机体验,也不会把个人开发证书和发布流水线耦合在一起。