Swift钥匙串-存储OAuth凭据:“钥匙串中已存在指定的项目”

时间:2018-08-26 16:51:02

标签: oauth-2.0 swift4 keychain

我正在为我的网站构建一个iOS应用,并且尝试使用OAuth2管理登录凭据。在用户登录时,我使用提供的用户名和密码成功命中了身份验证端点,并且尝试将访问令牌和刷新令牌存储在钥匙串中,因此用户不必继续提供凭据。

按照这些来源的指示,我在将刷新令牌和访问令牌存储到我的钥匙串时遇到了麻烦:

我能够成功存储访问令牌或刷新令牌,但是无论我先存储哪个令牌,当尝试存储另一个令牌时,我都会收到以下错误消息:“指定的项目已存在于钥匙串。”

我添加了一个CheckForExisting函数来删除任何具有相同规格的现有项,但是当我尝试使用相同的query来删除现有钥匙串项时,我会收到errSecItemNotFound状态。因此,很令人沮丧的是,我被告知无法创建我的项目,因为它已经存在,但是我无法删除现有项目,因为不存在任何现有项目。

我的假设是访问令牌项的创建会阻止刷新令牌项的创建,因此我希望有人可以对以下内容有所了解:

  1. 为什么第二项创建被阻止?钥匙串是否有一些我要打的内置主键检查(例如不能存储多个kSecClassInternetPassword)?
  2. 区分两个标记的正确方法是什么?现在我正在使用kSecAttrLabel,但这是黑暗中的镜头。

请注意,我希望解释为什么为什么当前的方法失败。我绝对欢迎替代实现,但是我真的很想了解这里幕后发生的事情,因此,如果可能的话,请说明替代实现避免了我似乎已经沦为陷阱的陷阱的地方。

用于存储令牌的Swift4代码:

func StoreTokens(username: String, access_token: String, refresh_token: String) throws {
    func CheckForExisting(query: [String: Any]) throws {
        let status = SecItemDelete(query as CFDictionary)
        guard status == errSecSuccess || status == errSecItemNotFound else {
            let error_message = SecCopyErrorMessageString(status, nil)!
            throw KeychainError.unhandledError(status: error_message)
        }
    }

    let configuration = ConfigurationDetails()

    let server = configuration.server
    let access_token = access_token.data(using: String.Encoding.utf8)!
    let refresh_token = refresh_token.data(using: String.Encoding.utf8)!
    let access_token_query: [String: Any] = [
        kSecClass as String: kSecClassInternetPassword,
        kSecAttrAccount as String: username,
        kSecAttrServer as String: server,
        kSecAttrLabel as String: "AccessToken",
        kSecValueData as String: access_token
    ]

    let refresh_token_query: [String: Any] = [
        kSecClass as String: kSecClassInternetPassword,
        kSecAttrAccount as String: username,
        kSecAttrServer as String: server,
        kSecAttrLabel as String: "RefreshToken",
        kSecValueData as String: refresh_token
    ]

    try CheckForExisting(query: access_token_query)
    let access_status = SecItemAdd(access_token_query as CFDictionary, nil)
    guard access_status == errSecSuccess else {
        let error_message = SecCopyErrorMessageString(access_status, nil)!
        throw KeychainError.unhandledError(status: error_message)
    }

    try CheckForExisting(query: refresh_token_query)
    let refresh_status = SecItemAdd(refresh_token_query as CFDictionary, nil)
    guard refresh_status == errSecSuccess else {
        let error_message = SecCopyErrorMessageString(refresh_status, nil)!
        throw KeychainError.unhandledError(status: error_message)
    }
}

1 个答案:

答案 0 :(得分:0)

根据此https://developer.apple.com/documentation/security/errsecduplicateitem看起来,类kSecClassInternetPassword的唯一键仅包含以下属性: kSecAttrAccount,kSecAttrSecurityDomain,kSecAttrServer,kSecAttrProtocol,kSecAttrAuthenticationType,kSecAttrPort和kSecAttrPath。

因此,kSecAttrLabel不在列表中,并且您的refresh_token_query复制了access_token_query。