我们通过以下功能检索任何已保存的密码:
SecRequestSharedWebCredential(NULL, NULL, ^(CFArrayRef credentials, CFErrorRef error) {
if (!error && CFArrayGetCount(credentials)) {
CFDictionaryRef credential = CFArrayGetValueAtIndex(credentials, 0);
if (credential > 0) {
CFDictionaryRef credential = CFArrayGetValueAtIndex(credentials, 0);
NSString *username = CFDictionaryGetValue(credential, kSecAttrAccount);
NSString *password = CFDictionaryGetValue(credential, kSecSharedPassword);
dispatch_async(dispatch_get_main_queue(), ^{
//Updates the UI here.
});
}
}
});
问题是在IOS 9.3.3 iPhone 6 A1524上,我们收到一个名为“密码未保存”的条目提示。没有错误消息表明未找到密码。因为数组> 0,它用条目完成表单。
为什么会这样?我们认为如果您的授权域名下没有存储密码,则不会出现提示。
有什么建议吗?
谢谢。
答案 0 :(得分:1)
我正在viewDidLoad()
为我的Auth视图控制器检查这个。代码与上面的内容略有不同,从其他几个SO答案中收集。
斯威夫特3:
SecRequestSharedWebCredential(Configuration.webBaseFQDN as CFString, nil, { (credentials, error) in
if let error = error {
print("ERROR: credentials")
print(error)
}
guard let credentials = credentials, CFArrayGetCount(credentials) > 0 else {
// Did not find a shared web credential.
return
}
guard CFArrayGetCount(credentials) == 1 else {
// There should be exactly one credential.
return
}
let unsafeCredential = CFArrayGetValueAtIndex(credentials, 0)
let credential = unsafeBitCast(unsafeCredential, to: CFDictionary.self)
let unsafeEmail = CFDictionaryGetValue(credential, Unmanaged.passUnretained(kSecAttrAccount).toOpaque())
let email = unsafeBitCast(unsafeEmail, to: CFString.self) as String
let unsafePassword = CFDictionaryGetValue(credential, Unmanaged.passUnretained(kSecSharedPassword).toOpaque())
let password = unsafeBitCast(unsafePassword, to: CFString.self) as String
if self.isValidEmail(email) && self.isValidPassword(password) {
self.usedSharedWebCredentials = true
self.doSignIn(email: email, password: password)
}
})
isValidEmail(_:)
和isValidPassword(_:)
末尾的额外检查会处理SecRequeestSharedWebCredential()
在第一个凭据(电子邮件)中返回“未保存的密码”的情况。
希望有人可以解释为什么会这样,但如果没有,至少有一种方法来捕捉这种情况。
我还想补充一点,我已经看到了iOS 10.2.1
答案 1 :(得分:0)
我遇到了同样的问题,并想补充一点,“未保存的密码”中的空格不是真实的空格。不知道为什么,从CFString
转换时可能有些奇怪。
无论哪种方式,由于Apple documentation仍在ObjC中,并且它们的安全性框架仍然是CoreFoundation繁重的,所以我认为最好发布我为共享Web编写的整个Swift 5代码凭证包装器。
它具有很好的错误管理逻辑(可以调整,因为您可能没有相同的ErrorBuilder API)。关于奇怪的空间,当从Xcode复制到StackOverflow时,它们变成了实空间,因此在String扩展中还有额外的逻辑。
从我所看到的内容来看,没有什么比这更好的了。
//
// CredentialsRepository.swift
// Created by Alberto De Bortoli on 26/07/2019.
//
import Foundation
public typealias Username = String
public typealias Password = String
public struct Credentials {
public let username: Username
public let password: Password
}
public enum GetCredentialsResult {
case success(Credentials)
case cancelled
case failure(Error)
}
public enum SaveCredentialsResult {
case success
case failure(Error)
}
protocol CredentialsRepository {
func saveCredentials(_ credentials: Credentials, completion: @escaping (SaveCredentialsResult) -> Void)
func getCredentials(completion: @escaping (GetCredentialsResult) -> Void)
}
//
// SharedWebCredentialsController.swift
// Created by Alberto De Bortoli on 26/07/2019.
//
class SharedWebCredentialsController {
let domain: String
init(domain: String) {
self.domain = domain
}
}
extension SharedWebCredentialsController: CredentialsRepository {
func getCredentials(completion: @escaping (GetCredentialsResult) -> Void) {
SecRequestSharedWebCredential(domain as CFString, .none) { cfArrayCredentials, cfError in
switch (cfArrayCredentials, cfError) {
case (_, .some(let cfError)):
let underlyingError = NSError(domain: CFErrorGetDomain(cfError) as String,
code: CFErrorGetCode(cfError),
userInfo: (CFErrorCopyUserInfo(cfError) as? Dictionary))
let error = ErrorBuilder.error(forCode: .sharedWebCredentialsFetchFailure, underlyingError: underlyingError)
DispatchQueue.main.async {
completion(.failure(error))
}
case (.some(let cfArrayCredentials), _):
if let credentials: [[String: String]] = cfArrayCredentials as? [[String: String]], credentials.count > 0,
let entry = credentials.first,
// let domain = entry[kSecAttrServer as String]
let username = entry[kSecAttrAccount as String],
let password = entry[kSecSharedPassword as String] {
DispatchQueue.main.async {
if username.isValidUsername() {
completion(.success(Credentials(username: username, password: password)))
}
else {
let error = ErrorBuilder.error(forCode: .sharedWebCredentialsFetchFailure, underlyingError: nil)
completion(.failure(error))
}
}
}
fallthrough
default:
DispatchQueue.main.async {
completion(.cancelled)
}
}
}
}
func saveCredentials(_ credentials: Credentials, completion: @escaping (SaveCredentialsResult) -> Void) {
SecAddSharedWebCredential(domain as CFString, credentials.username as CFString, credentials.password as CFString) { cfError in
switch cfError {
case .some(let cfError):
let underlyingError = NSError(domain: CFErrorGetDomain(cfError) as String,
code: CFErrorGetCode(cfError),
userInfo: (CFErrorCopyUserInfo(cfError) as? Dictionary))
let error = ErrorBuilder.error(forCode: .sharedWebCredentialsSaveFailure, underlyingError: underlyingError)
DispatchQueue.main.async {
completion(.failure(error))
}
default:
DispatchQueue.main.async {
completion(.success)
}
}
}
}
}
extension String {
fileprivate func isValidUsername() -> Bool {
// https://stackoverflow.com/questions/38698565/secrequestsharedwebcredential-credentials-contains-passwords-not-saved
// don't touch the 'Passwords not saved', the spaces are not what they seem (value copied from debugger)
guard self != "Passwords not saved" else { return false }
let containsAllInvalidWords = contains("Passwords") && contains("not") && contains("saved")
return !containsAllInvalidWords
}
}