Swift下将凭据保存至Apple Keychain失败,求解决方案
Apple Keychain 保存凭据失败问题排查
我想给用户提供把邮箱和密码保存到Apple Keychain的功能,但这个功能没法正常工作。下面是我用来向Keychain添加SecItem的函数,哪怕传入硬编码的字符串,在设备上调用后去检查Keychain,也看不到任何保存的内容。
func saveCredentialsToKeychain(username: String, password: String) throws { let account = username let password = password.data(using: String.Encoding.utf8)! var query: [String: Any] = [kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: account, kSecValueData as String: password] let status = SecItemAdd(query as CFDictionary, nil) guard status == errSecSuccess else { throw KeychainError.unhandledError(status: status) } }
核心排查点
- 优先检查错误状态码:当前代码只抛出错误但未区分具体状态。比如
errSecDuplicateItem表示该账号凭据已存在,此时应该用SecItemUpdate而非SecItemAdd——这是最常见的保存失败原因。 - 确认Keychain访问权限:iOS 10+ 需确保App的签名和Entitlements配置正确。若无需跨App共享凭据,需确保
keychain-access-groups包含当前App的Bundle ID;开发环境下要确认Xcode自动配置的Entitlements文件有效。 - 添加服务标识字段:
kSecClassGenericPassword建议补充kSecAttrService参数,用于区分不同服务的凭据,避免和其他条目冲突。例如:kSecAttrService as String: "com.yourcompany.yourapp.auth" - 实现“存在则更新”逻辑:Keychain不允许重复添加同一账号的凭据,所以保存前应先查询是否存在,再选择添加或更新:
func saveCredentialsToKeychain(username: String, password: String) throws { let account = username guard let passwordData = password.data(using: .utf8) else { throw KeychainError.invalidPassword } // 先查询是否存在 let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: account, kSecMatchLimit as String: kSecMatchLimitOne, kSecReturnAttributes as String: kCFBooleanTrue! ] var existingItem: CFTypeRef? let queryStatus = SecItemCopyMatching(query as CFDictionary, &existingItem) switch queryStatus { case errSecSuccess: // 存在则更新 let updateQuery: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: account ] let updates: [String: Any] = [kSecValueData as String: passwordData] let updateStatus = SecItemUpdate(updateQuery as CFDictionary, updates as CFDictionary) guard updateStatus == errSecSuccess else { throw KeychainError.unhandledError(status: updateStatus) } case errSecItemNotFound: // 不存在则添加 let addQuery: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: account, kSecValueData as String: passwordData, kSecAttrService as String: "com.yourcompany.yourapp.auth" ] let addStatus = SecItemAdd(addQuery as CFDictionary, nil) guard addStatus == errSecSuccess else { throw KeychainError.unhandledError(status: addStatus) } default: throw KeychainError.unhandledError(status: queryStatus) } } - 验证查看方式正确性:若通过系统“设置-密码”查看,需确保App开启了自动填充密码权限,且凭据符合自动填充规则;模拟器可通过Xcode的调试工具查看Keychain,真机需用开发者权限工具或越狱设备验证,避免因查看方式错误误以为未保存。
内容的提问来源于stack exchange,提问作者TyplerB
相关产品推荐
相关产品推荐

