使用 Swift 在 iOS 上将身份验证凭据添加到钥匙串

Adding auth credentials to Keychain on iOS with Swift

我正在尝试在登录过程中使用 iOS 的钥匙串库(针对 iphone)存储身份验证凭据。另外,我试图在 kSecClassKey 中存储 session_id,在 kSecAttrAccount 中存储用户 ID。这个解决方案感觉有点老套,但在研究问题(如何在 iOS 中存储会话数据)之后它似乎也是最好的解决方案。

编辑:我想我只是错误地使用了钥匙串。这感觉不对。如果有人在这里有任何建议,请告诉我,但我可能会很快删除它。

代码如下所示:

loginUser() {
    ...
    let account = user_id
    let server = "www.foo.com"
    let token = auth_token

    let query: [String: Any] = [
        kSecClass as String: kSecClassInternetPassword,
        kSecAttrAccount as String: account,
        kSecAttrServer as String: server,
        kSecClassKey as String: token
    ]

    let status = SecItemAdd(query as CFDictionary, nil)

    print(status)
}

当我 运行 该代码时,状态打印 -50。根据 osstatus,此代码可能有多种含义,包括 errSecNoSuchClasserrSecUseKeychainListUnsupportederrSecUseKeychainUnsupported.

我正在关注 this tutorial 来自 Apple 的文档。我不太确定如何从这里开始。有什么想法吗?

仅出于 learning/teaching 目的,我将编写如何手动使用钥匙串。但是,我会考虑使用库,因为 API 是一个相当低级别的 C-API..


首先你需要创建一个函数来检查该项目是否已经存在.. 如果它存在,您需要 "update" 该项目。如果不存在,需要添加..

下面的代码 (Swift 4.2) 应该可以做到。也没有必要将数据存储为 Internet 密码。您可以将其存储为通用密码。但这部分已经完成给你。

我在下面使用了通用密码,我只是存储了一个以 "user" 作为键的字典。

所以代码:

static let keychainIdentifier = "com.myproject.keychain.identifier"

// Checks if an item exists in the keychain already..
func exists(key: String, completion: @escaping (_ error: OSStatus, _ query: [CFString: Any], _ result: [CFString: Any]?) -> Void) {
    var query: [CFString: Any] = [
        kSecClass: kSecClassGenericPassword,
        kSecAttrGeneric: keychainIdentifier.data(using: .utf8)!
        kSecMatchLimit: kSecMatchLimitOne,
        kSecReturnAttributes: kCFBooleanTrue,
        kSecAttrAccessible: kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
        kSecAttrAccount: key
    ]

    var result: CFTypeRef?
    let error = SecItemCopyMatching(accountQuery as CFDictionary, &result)
    completion(error, accountQuery, result as? [CFString: Any])
}

// Adds or Updates an item in the keychain..
func setItem(key: String, data: Data) throws {
    exists(key: key) { error, query, _ in
        var query = query
        query[kSecMatchLimit] = nil
        query[kSecReturnAttributes] = nil

        if error == noErr || error == errSecDuplicateItem || error == errSecInteractionNotAllowed {
            let updateQuery = [kSecValueData: data]
            let err = SecItemUpdate(query as CFDictionary, updateQuery as CFDictionary)
            if err != noErr {
                throw KeychainError((code: err, message: "Cannot Update Item")
            }
        } 
        else if error == errSecItemNotFound {
            query[kSecValueData] = data
            let err = SecItemAdd(query as CFDictionary, nil)
            if err != noErr {
                throw KeychainError((code: err, message: "Cannot Set Item")
            }
        } else {
            if err != noErr {
                throw KeychainError(code: err, message: "Error Occurred")
            }
        }
    }
}

// Convenience function to save JSON to the keychain
func setItem(key: String, data: Codable) throws {
    let encodedData = try JSONEncoder().encode(data)
    setItem(key: key, data: encodedData)
}

和用法:

struct User: Codable {
    let username: String
    let token: String?
    let otherInfo: String
}

let user = User(username: "Brandon", token: "...", otherInfo: "...")
setItem(key: user.username, data: user)

IMO,这使它更容易使用..但要注意,你真的不应该在钥匙串中存储 "A LOT" 数据,例如整个模型或其他东西(作为通用密码) ..

或者,如果只是令牌,则:

setItem(key: user.username, data: "...".data(using: .utf8)!)