使用 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,此代码可能有多种含义,包括 errSecNoSuchClass
、errSecUseKeychainListUnsupported
或 errSecUseKeychainUnsupported
.
我正在关注 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)!)
我正在尝试在登录过程中使用 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,此代码可能有多种含义,包括 errSecNoSuchClass
、errSecUseKeychainListUnsupported
或 errSecUseKeychainUnsupported
.
我正在关注 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)!)