我使用ios钥匙串(keychainItemWrapper
/ SSKeychain
)来存储我应用的登录令牌并保持登录状态。目前,我在包含我的令牌,令牌到期和刷新令牌的钥匙串中存储了一个简单的NSDictionary
。我将它序列化为NSData并使用kSecValueData
进行存储。我还设置了kSecAttrAccount
和kSecAttrService
,但不要将其用于身份验证。
这很好用,大约95%的时间。问题是随机,不可预测和零星地,当我请求它来检索令牌时,钥匙串不会返回数据。通常在距离应用程序适度的时间之后重新打开它。它不必是在后台,或在任何特定延迟之后。
在下面询问我的NSData
并返回<>
而不是<ABCD EFGH IJKL ....>
时,它失败了。我认为这是零。因此,代码认为用户没有登录并立即将其丢弃在我的App的注册/登录登录页面上,没有注销错误,令牌到期错误等。如果我最小化应用程序,然后重新打开,它几乎总是得到正确的钥匙串信息,用户再次登录。
遇到这会产生令人困惑的体验。这也意味着用户无法保持这种真正的100%登录状态,偶尔会被随机注销。我已经无法预测或调试它,更改钥匙串库,如下所示,并没有为我修复它。它适用于我和几个TestFlight用户,以及我们目前的生产应用程序。
如何维护钥匙串的完整性并加载100%的时间?我们准备在令牌上实施NSUserDefaults备份存储,以便在这些情况下使用,这是我真的不想做的事情来存储身份验证令牌。
储存:
// load keychain
KeychainItemWrapper *keychainItem = [KeychainItemWrapper keyChainWrapperForKeyID:kcIdentifier];
NSString *firstLaunch = [keychainItem objectForKey: (__bridge id)(kSecAttrAccount)];
if (firstLaunch == nil){
// initialize if needed
[keychainItem setObject:email forKey: (__bridge id)(kSecAttrAccount)];
[keychainItem setObject:kcIdentifier forKey: (__bridge id)kSecAttrService];
[keychainItem setObject:(id)kSecAttrAccessibleAfterFirstUnlock forKey:(id)kSecAttrAccessible];
}
// serialize "auth" NSDictionary into NSData and store
NSString *error;
NSData *dictionaryData = [NSPropertyListSerialization dataFromPropertyList:auth format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
[keychainItem setObject:dictionaryData forKey:(id)kSecValueData];
装载:
// after similar KeychainItemWrapper initialization as above
NSData *dictionaryData = [keychainItem objectForKey:(id)kSecValueData];
NSString *error;
NSDictionary *auth = [NSPropertyListSerialization propertyListFromData:dictionaryData mutabilityOption:NSPropertyListImmutable format:nil errorDescription:&error];
NSString *token = auth[@"access_token"];
我也尝试使用广泛使用的SSKeychain
库CocoaPod,以及密钥链逻辑的包装器。这是一个更干净的访问,但失败了同样的问题。在这里,我只是存储NSString
值,因为没有直接的方法将NSData
存储在lib中。
// store in keychain
[SSKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlock];
[SSKeychain setPassword:auth[@"access_token"] forService:SSKEYCHAIN_SERVICE account:SSKEYCHAIN_TOKEN];
[SSKeychain setPassword:auth[@"expires_at"] forService:SSKEYCHAIN_SERVICE account:SSKEYCHAIN_EXPIRES_AT];
[SSKeychain setPassword:auth[@"refresh_token"] forService:SSKEYCHAIN_SERVICE account:SSKEYCHAIN_REFRESH_TOKEN];
// load from keychain
[SSKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlock];
NSString *token = [SSKeychain passwordForService:SSKEYCHAIN_SERVICE account:SSKEYCHAIN_TOKEN];
NSString *expires_at = [SSKeychain passwordForService:SSKEYCHAIN_SERVICE account:SSKEYCHAIN_EXPIRES_AT];
NSString *refresh_token = [SSKeychain passwordForService:SSKEYCHAIN_SERVICE account:SSKEYCHAIN_REFRESH_TOKEN];
答案 0 :(得分:3)
Keychain目前确实存在问题,而且相当长一段时间。这听起来像你正在轻松下车,因为它通常会在应用程序中退出时需要将其恢复生命。
有一点有用的是在第一个请求上只访问一次钥匙串,然后将结果缓存到内存中,如果它已经在内存中,那么只需从那里返回。
如果您在发生这种情况时可以观察到特定的错误,那么将其捕获并重试,或者就像当前某些不幸的应用程序的情况一样,杀死应用程序。如果您提出技术票据来与他们讨论问题,那么杀死该应用实际上是Apple当前的指导。
唯一的另一个真正的解决方案是加密数据并将其存储在一个文件中,但是你遇到了加密密钥的问题,所以这比针对敏锐攻击者的混淆要好一些。