116 lines
3.8 KiB
Swift
116 lines
3.8 KiB
Swift
import Foundation
|
||
import Security
|
||
|
||
/// 用于安全存储和检索敏感信息(如令牌)的 Keychain 帮助类
|
||
public class KeychainHelper {
|
||
// Keychain 键名
|
||
private enum KeychainKey: String, CaseIterable {
|
||
case accessToken = "com.memorywake.accessToken"
|
||
case refreshToken = "com.memorywake.refreshToken"
|
||
}
|
||
|
||
/// 保存访问令牌到 Keychain
|
||
public static func saveAccessToken(_ token: String) -> Bool {
|
||
return save(token, for: .accessToken)
|
||
}
|
||
|
||
/// 从 Keychain 获取访问令牌
|
||
public static func getAccessToken() -> String? {
|
||
return get(for: .accessToken)
|
||
}
|
||
|
||
/// 保存刷新令牌到 Keychain
|
||
public static func saveRefreshToken(_ token: String) -> Bool {
|
||
return save(token, for: .refreshToken)
|
||
}
|
||
|
||
/// 从 Keychain 获取刷新令牌
|
||
public static func getRefreshToken() -> String? {
|
||
return get(for: .refreshToken)
|
||
}
|
||
|
||
/// 删除所有存储的令牌
|
||
public static func clearTokens() {
|
||
delete(for: .accessToken)
|
||
delete(for: .refreshToken)
|
||
}
|
||
|
||
/// 清除所有存储的 Keychain 数据
|
||
public static func clearAll() {
|
||
// 清除所有已知的 keychain 项
|
||
KeychainKey.allCases.forEach { key in
|
||
delete(for: key)
|
||
}
|
||
|
||
// 额外清理:删除所有通用密码项(作为安全措施)
|
||
let query: [String: Any] = [
|
||
kSecClass as String: kSecClassGenericPassword,
|
||
kSecReturnAttributes as String: true,
|
||
kSecMatchLimit as String: kSecMatchLimitAll
|
||
]
|
||
|
||
var result: AnyObject?
|
||
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
||
|
||
if status == errSecSuccess, let items = result as? [[String: Any]] {
|
||
for item in items {
|
||
if let account = item[kSecAttrAccount as String] as? String,
|
||
let service = item[kSecAttrService as String] as? String {
|
||
let deleteQuery: [String: Any] = [
|
||
kSecClass as String: kSecClassGenericPassword,
|
||
kSecAttrAccount as String: account,
|
||
kSecAttrService as String: service
|
||
]
|
||
SecItemDelete(deleteQuery as CFDictionary)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - 私有方法
|
||
|
||
private static func save(_ string: String, for key: KeychainKey) -> Bool {
|
||
guard let data = string.data(using: .utf8) else { return false }
|
||
|
||
let query: [String: Any] = [
|
||
kSecClass as String: kSecClassGenericPassword,
|
||
kSecAttrAccount as String: key.rawValue,
|
||
kSecValueData as String: data
|
||
]
|
||
|
||
// 先删除已存在的项目
|
||
SecItemDelete(query as CFDictionary)
|
||
|
||
// 添加新项目
|
||
let status = SecItemAdd(query as CFDictionary, nil)
|
||
return status == errSecSuccess
|
||
}
|
||
|
||
private static func get(for key: KeychainKey) -> String? {
|
||
let query: [String: Any] = [
|
||
kSecClass as String: kSecClassGenericPassword,
|
||
kSecAttrAccount as String: key.rawValue,
|
||
kSecReturnData as String: true,
|
||
kSecMatchLimit as String: kSecMatchLimitOne
|
||
]
|
||
|
||
var dataTypeRef: AnyObject?
|
||
let status = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
|
||
|
||
guard status == errSecSuccess, let data = dataTypeRef as? Data else {
|
||
return nil
|
||
}
|
||
|
||
return String(data: data, encoding: .utf8)
|
||
}
|
||
|
||
private static func delete(for key: KeychainKey) {
|
||
let query: [String: Any] = [
|
||
kSecClass as String: kSecClassGenericPassword,
|
||
kSecAttrAccount as String: key.rawValue
|
||
]
|
||
|
||
SecItemDelete(query as CFDictionary)
|
||
}
|
||
}
|