7

我正在使用 ios 钥匙串 ( keychainItemWrapper/ SSKeychain) 来存储我的应用程序的登录令牌并保持登录状态。目前,我在钥匙串中存储了一个简单NSDictionary的令牌,其中包含我的令牌、令牌到期和刷新令牌。我将它序列化为 NSData 并使用kSecValueData. 我还设置了kSecAttrAccountand kSecAttrService,但不要将它们用于身份验证。

这很有效,大约 95% 的时间。问题是,当我请求它检索令牌时,钥匙串随机、不可预测和零星地不返回数据。重新打开应用程序时,通常是在离开应用程序一段时间后。它不必来自后台,也不必在任何特定延迟之后。

它在询问我的NSData下方并返回<>而不是返回时特别失败<ABCD EFGH IJKL ....>。我认为它是零。因此,代码认为用户没有登录并立即将它们放在我的应用程序的注册/登录登录页面上,没有注销错误、令牌过期错误等。如果我最小化应用程序,然后重新打开,它几乎总是得到正确的钥匙串info 并且用户再次登录。

这在遇到时会产生令人困惑的体验。这也意味着用户无法保持这种真正的 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在库中。

// 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];
4

1 回答 1

4

Keychain 目前确实存在问题,而且确实存在相当长的一段时间。听起来您像往常一样轻而易举地下车,因为通常需要强制退出应用程序才能使其恢复活力。

有帮助的一件事是在第一次请求时只访问一次钥匙串,然后将结果缓存在内存中,如果它已经在内存中,那么就从那里返回它。

如果您在发生这种情况时可以观察到特定错误,则捕获它并重试,或者像某些不幸的应用程序的当前情况一样,终止该应用程序。如果您提出技术票与他们讨论该问题,则终止该应用程序实际上是 Apple 当前的指导。

唯一其他真正的解决方案是加密数据并将其存储在一个文件中,但是您会遇到加密密钥的问题,因此这比对敏锐的攻击者进行模糊处理好不了多少。

于 2016-05-20T21:21:52.050 回答