2016-05-16 42 views
6

我使用ios钥匙串(keychainItemWrapper/SSKeychain)来存储我的应用程序的登录令牌并保持登录状态。目前,我在包含我的令牌,令牌过期和刷新令牌的钥匙串中存储简单的NSDictionary。我将它序列化为NSData并使用kSecValueData进行存储。我还设置了kSecAttrAccountkSecAttrService,但不要将这些用于身份验证。ios在KeyChain中存储登录令牌无法检索,很少和随机,但始终如一

这个工程的时候很大,约95%。问题在于,随机地,不可预知地和偶尔地,当我请求它检索令牌时,钥匙串不返回数据。通常在离开应用程序一段适中的时间后,重新打开它。它不一定是来自后台,或者经过任何特定的延迟。

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

的时候遇到这将创建一个令人困惑的经历。这也意味着用户无法保持这种真正的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

如果钥匙串失败,您应该得到一个错误代码。它是什么? – Segev

+0

我有同样的问题,其中检索随机失败。这里有一个辅助方法 - 钥匙串错误代码字符串:https://gist.github.com/inorganik/f9971e65f71e037650b39b5f182e157e – inorganik

+0

我会尝试记录错误。它可能发生在我使用的库内部,它不暴露&错误参数。 – Miro

回答

3

钥匙串目前确实存在问题,并且确实有一段时间。这听起来就像通常在打破一种力量时一样轻松 - 要退出应用程序才能恢复生机。

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

如果发生此情况,然后陷阱并重试,或如一些不幸的应用程序的当前情况下,你可以观察到一个特定的错误,终止该应用。杀死应用程序实际上是苹果当前的指导,如果你筹集技术票据与他们讨论问题。

唯一的其他的解决方法就是对数据进行加密,并将其存储在一个文件中,但你必须用加密密钥的问题,所以这是不是对敏锐的攻击者混淆更好一点。

+0

是钥匙串,通常在多个线程中是线程安全的,或者我应该担心@synchronizing接入点?我似乎记得阅读它已被处理,至少可以通过SSKeychain处理。 – Miro

+0

它本身是线程安全的,SSKeychain更多的是一种数据类型和效率封装 – Wain