作者:紫木之阁_229 | 来源:互联网 | 2022-10-20 13:42
我将密码存储到iOS钥匙串中,以后再检索它们以在我的应用程序中实现“记住我”(自动登录)功能。
我围绕Security.framework
功能(SecItemCopyMatching()
,等等)实现了自己的包装器,直到iOS 12为止,它的运行都像是一种魅力。
现在,我正在测试我的应用程序不会与即将推出的iOS 13兼容,瞧瞧:
SecItemCopyMatching()
总是回来 .errSecItemNotFound
...即使我以前已经存储了要查询的数据。
我的包装是用静态特性方便地提供的价值类kSecAttrService
和kSecAttrAccount
组装查询字典时:
class LocalCredentialStore {
private static let serviceName: String = {
guard let name = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String else {
return "Unknown App"
}
return name
}()
private static let accountName = "Login Password"
// ...
我将使用以下代码将密码插入钥匙串:
/*
- NOTE: protectWithPasscode is currently always FALSE, so the password
can later be retrieved programmatically, i.e. without user interaction.
*/
static func storePassword(_ password: String, protectWithPasscode: Bool, completion: (() -> Void)? = nil, failure: ((Error) -> Void)? = nil) {
// Encode payload:
guard let dataToStore = password.data(using: .utf8) else {
failure?(NSError(localizedDescription: ""))
return
}
// DELETE any previous entry:
self.deleteStoredPassword()
// INSERT new value:
let protection: CFTypeRef = protectWithPasscode ? kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly : kSecAttrAccessibleWhenUnlocked
let flags: SecAccessCOntrolCreateFlags= protectWithPasscode ? .userPresence : []
guard let accessCOntrol= SecAccessControlCreateWithFlags(
kCFAllocatorDefault,
protection,
flags,
nil) else {
failure?(NSError(localizedDescription: ""))
return
}
let insertQuery: NSDictiOnary= [
kSecClass: kSecClassGenericPassword,
kSecAttrAccessControl: accessControl,
kSecValueData: dataToStore,
kSecUseAuthenticationUI: kSecUseAuthenticationUIAllow,
kSecAttrService: serviceName, // These two values identify the entry;
kSecAttrAccount: accountName // together they become the primary key in the Database.
]
let resultCode = SecItemAdd(insertQuery as CFDictionary, nil)
guard resultCode == errSecSuccess else {
failure?(NSError(localizedDescription: ""))
return
}
completion?()
}
...然后,我通过以下方式检索密码:
static func loadPassword(completion: @escaping ((String?) -> Void)) {
// [1] Perform search on background thread:
DispatchQueue.global().async {
let selectQuery: NSDictiOnary= [
kSecClass: kSecClassGenericPassword,
kSecAttrService: serviceName,
kSecAttrAccount: accountName,
kSecReturnData: true,
kSecUseOperationPrompt: "Please authenticate"
]
var extractedData: CFTypeRef?
let result = SecItemCopyMatching(selectQuery, &extractedData)
// [2] Rendez-vous with the caller on the main thread:
DispatchQueue.main.async {
switch result {
case errSecSuccess:
guard let data = extractedData as? Data, let password = String(data: data, encoding: .utf8) else {
return completion(nil)
}
completion(password) //
(我不认为我用来打任何电话的词典中的任何条目都具有不合适的价值……但也许直到现在我仍遗漏了一些只是为了“获得通过”的东西)
我已经建立了一个可以正常工作的项目(Xcode 11 beta)来说明问题的存储库。
密码存储总是成功;密码加载:
成功在Xcode中10 - 12的iOS(或更早),但
.errSecItemNotFound
在Xcode 11-iOS 13上无法使用。
更新:我无法在设备上重现该问题,只能在模拟器上重现。在设备上,成功检索到存储的密码。也许这是针对x86平台的iOS 13 Simulator和/或iOS 13 SDK上的错误或限制。
更新2:如果有人想出了一种解决问题的替代方法(无论是通过设计还是通过Apple的监督),我将接受它作为答案。