关于 maui-secure-storage
面向 .NET MAUI 开发者的安全存储指南,讲解 Android、iOS 和 Windows 上的风险与实践。帮助安全处理机密、考虑平台行为,并通过依赖注入让存储访问可测试。
主要功能
- 01平台特定的存储风险
- 02Android 备份与异常处理
- 03iOS Keychain 行为指南
- 04Windows 存储限制
- 05依赖注入测试模式
- 06机密处理检查清单
适用场景
- 01在 MAUI 应用中存储令牌和密码
- 02防止 Android 备份数据导致读取失败
- 03规划重装后仍保留的 Keychain 数据
- 04让 ViewModel 存储访问可模拟测试
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ davidortinau/ maui-skills --skill maui-secure-storage安装后在 Claude Code 中输入/maui-secure-storage确认已加载。
附带文件references/secure-storage-api.md
详细介绍
安全存储 — 注意事项与最佳实践
关键平台陷阱
⚠️ Android:自动备份会破坏加密值
自动备份会将加密后的偏好设置恢复到新设备,而加密密钥在新设备上无效——这会引发无法恢复的异常。你必须禁用自动备份,或将安全存储文件排除在备份之外。设置选项请参阅 references/secure-storage-api.md。
⚠️ Android:务必使用 try/catch 包裹
备份恢复产生的损坏值会引发异常。切勿在未受保护的情况下调用 GetAsync:
// ❌ Unprotected — crashes on corrupted backup data
var value = await SecureStorage.Default.GetAsync("key");
// ✅ Protected — handles corruption gracefully
try
{
var value = await SecureStorage.Default.GetAsync("key");
}
catch (Exception)
{
SecureStorage.Default.RemoveAll();
}
⚠️ iOS:模拟器上的钥匙串权限
为模拟器构建添加钥匙串访问组,但在实体设备 / App Store 构建前务必移除——在不需要这些权限的设备上,它们会导致签名问题。
⚠️ iOS:卸载不会清除钥匙串
与 Android 不同,卸载 iOS 应用不会移除其钥匙串条目。值会保留,并在应用重新安装后继续可用。请据此进行设计——不要假设全新安装意味着存储为空。
⚠️ iOS:iCloud 钥匙串同步
如果用户启用了 iCloud 钥匙串,值可能会通过它在设备间同步。这是平台行为,无法通过 MAUI 控制。不要存储不应在设备间漫游的设备专属令牌。
Windows 限制
- 键名:最多 255 个字符
- 值:每项最多 8 KB
- 复合存储:总计最多 64 KB
常见错误
// ❌ Storing large data — SecureStorage is for small secrets only
await SecureStorage.Default.SetAsync("profile_image", base64EncodedImage);
// ✅ Store tokens, passwords, short secrets
await SecureStorage.Default.SetAsync("auth_token", jwtToken);
// ❌ Logging secret values
_logger.LogInformation("Token: {Token}", await SecureStorage.Default.GetAsync("auth_token"));
// ✅ Log existence, not value
_logger.LogInformation("Token exists: {Exists}", token is not null);
// ❌ Storing complex objects without serialization (values are strings only)
await SecureStorage.Default.SetAsync("user", userObject);
// ✅ Serialize to JSON first
await SecureStorage.Default.SetAsync("user", JsonSerializer.Serialize(user));
决策框架
| 问题 | 答案 |
|---|---|
| 要存储令牌、密码或 API 密钥? | ✅ 使用 SecureStorage |
| 要存储用户偏好设置或配置? | ❌ 改用 Preferences |
| 要存储大型文件或二进制数据? | ❌ 使用文件系统 + 加密 |
| 需要跨设备同步? | ⚠️ iOS 会通过 iCloud 钥匙串自动同步 |
| 需要在卸载时清除数据? | ⚠️ 仅适用于 Android,不适用于 iOS |
可测试性:始终使用 DI
切勿在 ViewModel 中直接调用 SecureStorage.Default——请将其封装在 ISecureStorageService 接口中,以便进行测试。包含模拟示例的完整 DI 封装模式请参阅 references/secure-storage-api.md。
// ❌ Direct static access — untestable
public class LoginViewModel
{
public async Task SaveToken(string token)
=> await SecureStorage.Default.SetAsync("auth_token", token);
}
// ✅ Inject interface — testable and mockable
public class LoginViewModel(ISecureStorageService secure)
{
public async Task SaveToken(string token)
=> await secure.SetAsync("auth_token", token);
}
检查清单
- Android:已禁用自动备份,或已将安全存储排除在备份之外
- Android:所有
GetAsync调用均使用 try/catch 包裹 - iOS:已配置钥匙串权限(仅限模拟器——设备构建前移除)
- 值仅为字符串——复杂数据已序列化为 JSON
- 未在任何位置记录机密值
- 通过 DI 封装访问
SecureStorage.Default,而非在 ViewModel 中直接访问
评论
支持 Markdown