網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
正文
在我們開(kāi)發(fā)iOS應(yīng)用的時(shí)候,很多時(shí)候,我們都需要將敏感數(shù)據(jù)(password, accessToken, secretKey等)存儲(chǔ)到本地。對(duì)于初級(jí)程序員來(lái)講,首先映入腦海的可能是使用UserDefaults
。然而,眾所周知,使用UserDefaults
來(lái)存儲(chǔ)敏感信息簡(jiǎn)直是low的不能再low的主意了。因?yàn)槲覀円话愦鎯?chǔ)到UserDefaults
中的數(shù)據(jù)都是未經(jīng)過(guò)編碼處理的,這樣是非常不安全的。
為了能安全的在本地存儲(chǔ)敏感信息,我們應(yīng)當(dāng)使用蘋(píng)果提供的KeyChain
服務(wù)。這個(gè)framework已經(jīng)相當(dāng)老了,所以,我們?cè)诤竺骈喿x的時(shí)候,會(huì)覺(jué)得它提供的API并不像當(dāng)下的framework那么快捷。
在本文中,將為你展示如何創(chuàng)建一個(gè)通用的同時(shí)適用于iOS、MacOS的keyChain輔助類,對(duì)數(shù)據(jù)進(jìn)行增刪改查操作。開(kāi)始吧!!!
保存數(shù)據(jù)到KeyChain
final class KeyChainHelper { static let standard = KeyChainHelper() private init(){} }
我們必須巧妙使用SecItemAdd(_:_:)
方法,這個(gè)方法會(huì)接收一個(gè)CFDictionary
類型的query對(duì)象。
這個(gè)主意是為了創(chuàng)建一個(gè)query對(duì)象,這個(gè)對(duì)象包含了我們想要存儲(chǔ)最主要的數(shù)據(jù)鍵值對(duì)。然后,將query對(duì)象傳入SecItemAdd(_:_:)
方法中來(lái)執(zhí)行保存操作。
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對(duì)象由4個(gè)鍵值對(duì)組成:
-
kSecValueData
: 這個(gè)鍵代表著數(shù)據(jù)已經(jīng)被存儲(chǔ)到了keyChain中 -
kSecClass
: 這個(gè)鍵代表著數(shù)據(jù)已經(jīng)被存儲(chǔ)到了keyChain中。我們將它的值設(shè)為了kSecClassGenericPassword
,這代表著我們所保存的數(shù)據(jù)是一個(gè)通用的密碼項(xiàng) -
kSecAttrService
和kSecAttrAccount
: 當(dāng)kSecClass
被設(shè)置為kSecClassGenericPassword
的時(shí)候,kSecAttrService
和kSecAttrAccount
這兩個(gè)鍵是必須要有的。這兩個(gè)鍵所對(duì)應(yīng)的值將作為所保存數(shù)據(jù)的關(guān)鍵key,換句話說(shuō),我們將使用他們從keyChain中讀取所保存的值。
對(duì)于kSecAttrService
和kSecAttrAccount
所對(duì)應(yīng)的值的定義并沒(méi)有什么難的。推薦使用字符串。例如:如果我們想存儲(chǔ)Facebook的accesToken,我們需要將kSecAttrService
設(shè)置成”access-token“,將kSecAttrAccount
設(shè)置成”facebook“
創(chuàng)建完query對(duì)象之后,我們可以調(diào)用SecItemAdd(_:_:)
方法來(lái)保存數(shù)據(jù)到keyChain。SecItemAdd(_:_:)
方法會(huì)返回一個(gè)OSStatus
來(lái)代表存儲(chǔ)狀態(tài)。如果我們得到的是errSecSuccess
狀態(tài),則意味著數(shù)據(jù)已經(jīng)被成功保存到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中使用,所以,上述代碼必須寫(xiě)在Controller中。
更新KeyChain中已有的數(shù)據(jù)
現(xiàn)在我們有了save(_:service:account:)
方法,讓我們用相同的kSecAttrService
和kSecAttrAccount
所對(duì)應(yīng)的值來(lái)存儲(chǔ)其他token
let accessToken = "another-dummy-access-token" let data = Data(accessToken.utf8) KeychainHelper.standard.save(data, service: "access-token", account: "facebook")
這時(shí)候,我們就無(wú)法將accessToken保存到keyChain中了。同時(shí),我們會(huì)得到一個(gè)Error: -25299
的報(bào)錯(cuò)。該錯(cuò)誤碼代表的是存儲(chǔ)失敗。因?yàn)槲覀兯褂玫膋eys已經(jīng)存在于keyChain當(dāng)中了。
為了解決這個(gè)問(wèn)題,我們需要檢查這個(gè)錯(cuò)誤碼(相當(dāng)于errSecDuplicateItem
),然后使用SecItemUpdate(_:_:)
方法來(lái)更新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) } }
跟保存操作相似的是,我們需要先創(chuàng)建一個(gè)query對(duì)象,這個(gè)對(duì)象包含kSecAttrService
和kSecAttrAccount
。但是這次,我們將會(huì)創(chuàng)建另外一個(gè)包含kSecValueData
的字典,并將它傳給SecItemUpdate(_:_:)
方法。
這樣的話,我們就可以讓save(_:service:account:)
方法來(lái)更新keyChain中已有的數(shù)據(jù)了。
從KeyChain中讀取數(shù)據(jù)
從keyChain中讀取數(shù)據(jù)的方式和保存的方式非常相似。我們首先要做的是創(chuàng)建一個(gè)query對(duì)象,然后調(diào)用一個(gè)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) }
跟之前一樣,我們需要設(shè)置query對(duì)象的kSecAttrService
?and?kSecAttrAccount
的值。在這之前,我們需要為query對(duì)象添加一個(gè)新的鍵kSecReturnData
,其值為true
,代表的是我們希望query返回對(duì)應(yīng)項(xiàng)的數(shù)據(jù)。
之后,我們將利用 SecItemCopyMatching(_:_:)
方法并通過(guò)引用傳入 AnyObject
類型的result
對(duì)象。SecItemCopyMatching(_:_:)
方法同樣返回一個(gè)OSStatus類型的值,代表讀取操作狀態(tài)。但是如果讀取失敗了,這里我們不做任何校驗(yàn),并返回nil
讓keyChain支持讀取的操作就這么多了,看一下他是怎么工作的吧
let data = KeychainHelper.standard.read(service: "access-token", account: "facebook")! let accessToken = String(data: data, encoding: .utf8)! print(accessToken)
從KeyChain中刪除數(shù)據(jù)
如果沒(méi)有刪除操作,我們的KeyChainHelper類并不算完成。一起看看下面的代碼片段吧
func delete(service: String, account: String) { let query = [ kSecAttrService: service, kSecAttrAccount: account, kSecClass: kSecClassGenericPassword, ] as CFDictionary // Delete item from keychain SecItemDelete(query) }
如果你全程都在看的話,上述代碼可能對(duì)你來(lái)說(shuō)非常熟悉,那是相當(dāng)?shù)摹弊越忉尅傲耍枰⒁獾氖牵@里我們使用了SecItemDelete(_:)
方法來(lái)刪除KeyChain中的數(shù)據(jù)了。
創(chuàng)建一個(gè)通用的KeyChainHelper 類
存儲(chǔ)
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"
原文鏈接:https://juejin.cn/post/7195486949526732857
相關(guān)推薦
- 2022-06-26 Python使用Tkinter?GUI實(shí)現(xiàn)輸入驗(yàn)證功能_python
- 2022-04-08 記一次go語(yǔ)言使用time.Duration類型踩過(guò)的坑_Golang
- 2022-05-25 ASP.NET?MVC+EF實(shí)現(xiàn)異步增刪改查_(kāi)實(shí)用技巧
- 2022-08-18 nginx之queue的具體使用_nginx
- 2022-04-25 python遞歸&迭代方法實(shí)現(xiàn)鏈表反轉(zhuǎn)_python
- 2022-09-10 C++中memcpy函數(shù)的使用以及模擬實(shí)現(xiàn)_C 語(yǔ)言
- 2022-03-17 Golang動(dòng)態(tài)調(diào)用方法小結(jié)_Golang
- 2022-08-06 python列表去重的5種常見(jiàn)方法實(shí)例_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門(mén)
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支