如何解决Xamarin iOS中SecKeyChain的InteractionNotAllowed问题?
嘿,我来帮你捋清楚这个问题的根源,以及对应的解决办法!你遇到的这种“找不到项却提示重复、删也删不掉”的矛盾情况,本质是钥匙串项的Accessible属性不匹配导致的,咱们一步步拆解解决:
问题根源
你之前的钥匙串项是用默认的SecAccessible.WhenUnlocked存储的,后来为了解决InteractionNotAllowed错误,直接把代码里所有查询、新增、删除操作的Accessible属性改成了SecAccessible.Always。但钥匙串的查询逻辑是严格匹配所有指定属性的——你用Always的条件去查,根本找不到原来用WhenUnlocked存的项;可旧项实际还在钥匙串里,所以新增同Service+Account的项时会提示DuplicateItem,删除时又因为条件不匹配找不到项,就陷入了死循环。
解决步骤
1. 先兼容读取旧属性的项
首先修改查询逻辑,暂时不要在查询时指定Accessible属性,这样才能匹配到所有同Service和Account的项,不管它原来的Accessible是什么:
private SecRecord ExistingRecordForKey(string accountName) { return new SecRecord(SecKind.GenericPassword) { Service = App.AppName, Account = accountName // 移除Accessible的硬编码,让查询匹配所有属性的同账号项 }; }
2. 迁移旧项到新的Accessible属性
在读取方法里加入迁移逻辑:当读到旧属性的项时,取出值、删除旧项,再用SecAccessible.Always重新保存,这样后续就能统一用新属性操作了。同时还要修正你代码里的一个小bug——读取Generic字段时误用了ValueData:
public string GetValueFromAccountAndKey(string accountName, string key) { try { // 先查询不带Accessible属性的记录,覆盖旧项和新项 var record = new SecRecord(SecKind.GenericPassword) { Service = App.AppName, Account = accountName }; SecStatusCode resultCode; var match = SecKeyChain.QueryAsRecord(record, out resultCode); if (resultCode == SecStatusCode.Success) { string valueData = null; // 正确读取ValueData字段 if (match.ValueData != null) { valueData = NSString.FromData(match.ValueData, NSStringEncoding.UTF8); } // 正确读取Generic字段(之前的代码这里写错了) else if (match.Generic != null) { valueData = NSString.FromData(match.Generic, NSStringEncoding.UTF8); } if (!string.IsNullOrEmpty(valueData)) { // 如果项的属性不是Always,就执行迁移 if (match.Accessible != SecAccessible.Always) { // 删除旧属性的项 SecKeyChain.Remove(record); // 用Always重新保存 var newRecord = CreateRecordForNewKeyValue(accountName, valueData); SecKeyChain.Add(newRecord); } return valueData; } } } catch (Exception e) { // 保留你的日志逻辑 } return string.Empty; }
3. 修复新增/删除逻辑的兼容性
在SetValueForKeyAndAccount方法里,遇到DuplicateItem时,不要直接用带Always属性的记录去删除,而是用不带属性的查询条件找到对应项再删除:
// ... 原有代码 ... if (result == SecStatusCode.DuplicateItem) { try { // 用不带Accessible的查询条件找项 var duplicateRecord = new SecRecord(SecKind.GenericPassword) { Service = App.AppName, Account = accountName }; SecKeyChain.Remove(duplicateRecord); // 删除后重新添加新属性的项 SecKeyChain.Add(CreateRecordForNewKeyValue(accountName, value)); } catch (Exception e) { // 保留你的日志逻辑 } } // ... 原有代码 ...
4. 完成迁移后统一属性
等用户都完成一次读写操作(旧项全部迁移完成),你就可以把查询逻辑里的Accessible属性改回SecAccessible.Always,确保后续所有操作都用统一的属性。
额外提醒
苹果官方其实更推荐使用SecAccessible.AlwaysThisDeviceOnly(如果不需要跨设备同步),比Always更安全,你可以根据业务需求选择。
内容的提问来源于stack exchange,提问作者Vishnu

