当前位置: 代码网 > it编程>App开发>苹果IOS > iOS数据持久化KeyChain数据操作详解

iOS数据持久化KeyChain数据操作详解

2024年05月18日 苹果IOS 我要评论
正文在我们开发ios应用的时候,很多时候,我们都需要将敏感数据(password, accesstoken, secretkey等)存储到本地。对于初级程序员来讲,首先映入脑海的可能是使用userde

正文

在我们开发ios应用的时候,很多时候,我们都需要将敏感数据(password, accesstoken, secretkey等)存储到本地。对于初级程序员来讲,首先映入脑海的可能是使用userdefaults。然而,众所周知,使用userdefaults来存储敏感信息简直是low的不能再low的主意了。因为我们一般存储到userdefaults中的数据都是未经过编码处理的,这样是非常不安全的。

为了能安全的在本地存储敏感信息,我们应当使用苹果提供的keychain服务。这个framework已经相当老了,所以,我们在后面阅读的时候,会觉得它提供的api并不像当下的framework那么快捷。

在本文中,将为你展示如何创建一个通用的同时适用于ios、macos的keychain辅助类,对数据进行增删改查操作。开始吧!!!

保存数据到keychain

final class keychainhelper {
    static let standard = keychainhelper()
    private init(){}
}

我们必须巧妙使用secitemadd(_:_:)方法,这个方法会接收一个cfdictionary类型的query对象。

这个主意是为了创建一个query对象,这个对象包含了我们想要存储最主要的数据键值对。然后,将query对象传入secitemadd(_:_:)方法中来执行保存操作。

func save(_ data: data, service: string, account: string) {
    // create query
    let query = [
        ksecvaluedata: data,
        ksecclass: ksecclassgenericpassword,
        ksecattrservice: service,
        ksecattraccount: account,
    ] as cfdictionary
    // add data in query to keychain
    let status = secitemadd(query, nil)
    if status != errsecsuccess {
        // print out the error
        print("error: (status)")
    }
}

回看上述代码片段,query对象由4个键值对组成:

  • ksecvaluedata: 这个键代表着数据已经被存储到了keychain中
  • ksecclass: 这个键代表着数据已经被存储到了keychain中。我们将它的值设为了ksecclassgenericpassword,这代表着我们所保存的数据是一个通用的密码项
  • ksecattrserviceksecattraccount: 当ksecclass被设置为ksecclassgenericpassword的时候,ksecattrserviceksecattraccount这两个键是必须要有的。这两个键所对应的值将作为所保存数据的关键key,换句话说,我们将使用他们从keychain中读取所保存的值。

对于ksecattrserviceksecattraccount所对应的值的定义并没有什么难的。推荐使用字符串。例如:如果我们想存储facebook的accestoken,我们需要将ksecattrservice设置成”access-token“,将ksecattraccount设置成”facebook“

创建完query对象之后,我们可以调用secitemadd(_:_:)方法来保存数据到keychain。secitemadd(_:_:)方法会返回一个osstatus来代表存储状态。如果我们得到的是errsecsuccess状态,则意味着数据已经被成功保存到keychain中

下面是save(_:service:account:)方法的使用

let accesstoken = "dummy-access-token"
let data = data(accesstoken.utf8)
keychainhelper.standard.save(data, service: "access-token", account: "facebook")

keychain不能在playground中使用,所以,上述代码必须写在controller中。

更新keychain中已有的数据

现在我们有了save(_:service:account:)方法,让我们用相同的ksecattrserviceksecattraccount所对应的值来存储其他token

let accesstoken = "another-dummy-access-token"
let data = data(accesstoken.utf8)
keychainhelper.standard.save(data, service: "access-token", account: "facebook")

这时候,我们就无法将accesstoken保存到keychain中了。同时,我们会得到一个error: -25299的报错。该错误码代表的是存储失败。因为我们所使用的keys已经存在于keychain当中了。

为了解决这个问题,我们需要检查这个错误码(相当于errsecduplicateitem),然后使用secitemupdate(_:_:)方法来更新keychain。一起看看并更新我们前述的save(_:service:account:)方法吧:

func save(_ data: data, service: string, account: string) {
    // ... ...
    // ... ...
    if status == errsecduplicateitem {
        // item already exist, thus update it.
        let query = [
            ksecattrservice: service,
            ksecattraccount: account,
            ksecclass: ksecclassgenericpassword,
        ] as cfdictionary
        let attributestoupdate = [ksecvaluedata: data] as cfdictionary
        // update existing item
        secitemupdate(query, attributestoupdate)
    }
}

跟保存操作相似的是,我们需要先创建一个query对象,这个对象包含ksecattrserviceksecattraccount。但是这次,我们将会创建另外一个包含ksecvaluedata的字典,并将它传给secitemupdate(_:_:)方法。

这样的话,我们就可以让save(_:service:account:)方法来更新keychain中已有的数据了。

从keychain中读取数据

从keychain中读取数据的方式和保存的方式非常相似。我们首先要做的是创建一个query对象,然后调用一个keychain方法:

func read(service: string, account: string) -> data? {
    let query = [
        ksecattrservice: service,
        ksecattraccount: account,
        ksecclass: ksecclassgenericpassword,
        ksecreturndata: true
    ] as cfdictionary
    var result: anyobject?
    secitemcopymatching(query, &result)
    return (result as? data)
}

跟之前一样,我们需要设置query对象的ksecattrservice and ksecattraccount的值。在这之前,我们需要为query对象添加一个新的键ksecreturndata,其值为true,代表的是我们希望query返回对应项的数据。

之后,我们将利用 secitemcopymatching(_:_:) 方法并通过引用传入 anyobject 类型的result对象。secitemcopymatching(_:_:)方法同样返回一个osstatus类型的值,代表读取操作状态。但是如果读取失败了,这里我们不做任何校验,并返回nil

让keychain支持读取的操作就这么多了,看一下他是怎么工作的吧

let data = keychainhelper.standard.read(service: "access-token", account: "facebook")!
let accesstoken = string(data: data, encoding: .utf8)!
print(accesstoken)

从keychain中删除数据

如果没有删除操作,我们的keychainhelper类并不算完成。一起看看下面的代码片段吧

func delete(service: string, account: string) {
    let query = [
        ksecattrservice: service,
        ksecattraccount: account,
        ksecclass: ksecclassgenericpassword,
        ] as cfdictionary
    // delete item from keychain
    secitemdelete(query)
}

如果你全程都在看的话,上述代码可能对你来说非常熟悉,那是相当的”自解释“了,需要注意的是,这里我们使用了secitemdelete(_:)方法来删除keychain中的数据了。

创建一个通用的keychainhelper 类

存储

func save<t>(_ item: t, service: string, account: string) where t : codable {
    do {
        // encode as json data and save in keychain
        let data = try jsonencoder().encode(item)
        save(data, service: service, account: account)
    } catch {
        assertionfailure("fail to encode item for keychain: (error)")
    }
}

读取

func read<t>(service: string, account: string, type: t.type) -> t? where t : codable {
    // read item data from keychain
    guard let data = read(service: service, account: account) else {
        return nil
    }
    // decode json data to object
    do {
        let item = try jsondecoder().decode(type, from: data)
        return item
    } catch {
        assertionfailure("fail to decode item for keychain: \(error)")
        return nil
    }
}

使用

struct auth: codable {
    let accesstoken: string
    let refreshtoken: string
}
// create an object to save
let auth = auth(accesstoken: "dummy-access-token",
                 refreshtoken: "dummy-refresh-token")
let account = "domain.com"
let service = "token"
// save `auth` to keychain
keychainhelper.standard.save(auth, service: service, account: account)
// read `auth` from keychain
let result = keychainhelper.standard.read(service: service,
                                          account: account,
                                          type: auth.self)!
print(result.accesstoken)   // output: "dummy-access-token"
print(result.refreshtoken)  // output: "dummy-refresh-token"

以上就是ios数据持久化keychain的详细内容,更多关于ios数据持久化keychain的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com