iOS Keychain存储读取accessToken、refreshToken报错如何解决
代码问题分析与修正方案
核心错误原因
- 存储类型选择错误:你使用了
kSecClassKey作为存储类型,这个类型仅适用于存储加密密钥(如RSA密钥、AES对称密钥),普通字符串token应该使用通用密码类型kSecClassGenericPassword,这是导致store返回-50(参数错误)、load返回-25300(条目不存在)的根本原因 - 存储/查询参数不匹配:
- 存储时错误使用
kSecValueRef字段,普通字符串需要转成Data后存入kSecValueData字段 - 查询时额外添加了
kSecAttrKeyTypeRSA、kSecReturnRef等仅适用于密钥存储的参数,既不符合存储类型要求,也会导致查询条件和存储的条目不匹配
- 存储时错误使用
- 缺少重复存储处理逻辑:如果同一个key已经存在于Keychain中,直接调用
SecItemAdd会报错,需要先查询是否存在,存在则走更新逻辑,不存在再走新增逻辑
修正后的完整代码
import Security enum Key: String { case accessToken = "some.keys.accessToken" case refreshToken = "some.keys.refreshToken" } enum KeychainError: Error { case storeFailed case loadFailed case conversionFailed } func store(key: Key, value: String) throws { guard let valueData = value.data(using: .utf8) else { throw KeychainError.conversionFailed } // 先查询是否已存在对应条目 let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: key.rawValue ] let status = SecItemCopyMatching(query as CFDictionary, nil) if status == errSecSuccess { // 已存在则更新 let updateAttributes = [kSecValueData as String: valueData] let updateStatus = SecItemUpdate(query as CFDictionary, updateAttributes as CFDictionary) guard updateStatus == errSecSuccess else { print("Update key: '\(key.rawValue)' in Keychain failed with status: \(updateStatus)") throw KeychainError.storeFailed } } else if status == errSecItemNotFound { // 不存在则新增 let addQuery: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: key.rawValue, kSecValueData as String: valueData ] let addStatus = SecItemAdd(addQuery as CFDictionary, nil) guard addStatus == errSecSuccess else { print("Store key: '\(key.rawValue)' in Keychain failed with status: \(addStatus)") throw KeychainError.storeFailed } } else { print("Query key existence failed with status: \(status)") throw KeychainError.storeFailed } } func load(key: Key) throws -> String? { let getQuery: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: key.rawValue, kSecReturnData as String: true, kSecMatchLimit as String: kSecMatchLimitOne ] var item: CFTypeRef? let status = SecItemCopyMatching(getQuery as CFDictionary, &item) guard status == errSecSuccess, let data = item as? Data, let value = String(data: data, encoding: .utf8) else { if status != errSecItemNotFound { print("Load key: '\(key.rawValue)' in Keychain failed with status: \(status)") } throw KeychainError.loadFailed } return value }
可选优化
如果需要实现跨设备iCloud同步、访问控制限制(比如仅锁屏后可访问),可以在存储的query中额外添加对应的kSecAttrSynchronizable、kSecAttrAccessible参数即可。
内容的提问来源于stack exchange,提问作者Wiingaard
相关产品推荐
相关产品推荐

