2

我的应用程序在用户登录或登录时存储用户的凭据。当应用程序启动时,我会检查didFinishLaunchingWithOptions我们是否存储了凭据。当通过点击 App Icon 或从 Xcode 启动它来启动应用程序时,这可以正常工作。

但是,当应用程序在后台被杀死并由于位置更改更新而被系统重新启动时,返回的凭据defaultCredentialForProtectionSpace为 nil。当我再次正常重新启动应用程序时,凭据又回来了。

所以当[launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey]为真时,NSURLCredential返回的NSURLCredentialStorage是零;当它为假时,我们得到预期的凭证。

这是一些代码:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Other init things happen here, including setting up the NSURLProtectionSpace

    NSURLCredentialStorage *credentialStorage = [NSURLCredentialStorage sharedCredentialStorage];
    NSURLCredential *credential = [credentialStorage defaultCredentialForProtectionSpace:self.protectionSpace];
    if (credential) {
        // do something - XXX this does not happen when app is launched in background
    }
} 
4

1 回答 1

3

事实证明,钥匙串中的NSURLCredentialStorage集合kSecAttrAccessibleWhenUnlocked用于存储凭据。这意味着当用户设置密码以解锁手机并且手机被锁定时,无法访问凭据。

- -编辑 - -

以上解释了为什么它不起作用,这是我最终实施的解决方案:kSecAttrAccessible将钥匙串中的更改为kSecAttrAccessibleAfterFirstUnlock并处理在手机重新启动但尚未解锁的极少数情况下无法访问凭据。

当 App 处于前台时,钥匙串以NSURLCredentialStorage:使用的模式解锁kSecAttrAccessibleWhenUnlocked。因此,applicationDidBecomeActive:我们可以从中访问钥匙串条目NSURLCredentialStorage并进行更改:

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    KeychainItemWrapper *wrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@"server.com" accessGroup:nil];
    [wrapper setObject:(__bridge id)kSecAttrAccessibleAfterFirstUnlock forKey:(__bridge id)kSecAttrAccessible];

    // ...
}

上面的代码使用了 KeychainItemWrapper 的改编版本。原始版本来自 Apple,但我的解决方案基于此处的 ARCified 版本:https ://gist.github.com/dhoerl/1170641 。您仍然必须更改该版本才能使用kSecClassInternetPassword而不是kSecClassGenericPassword,这主要意味着您不能使用kSecAttrGeneric来查询钥匙串项目,而是使用kSecAttrServer.

- -编辑 - -

上述方法在 iOS 7.x 上运行良好,但在 iOS 8.x 上不再适用。代码运行良好,但钥匙串仍然使用kSecAttrAccessibleWhenUnlocked.

现在唯一的解决方案是NSURLCredentialStorage仅在您知道只会在前台使用它时使用。否则,您需要使用来自https://gist.github.com/dhoerl/1170641的 KeychainItemWrapper 自己将凭据存储在 Keychain 中。然后您可以kSecAttrAccessibleAfterFirstUnlock在存储凭据时自行设置。

示例代码:

KeychainItemWrapper *keychain = [[KeychainItemWrapper alloc] initWithIdentifier:@"YOUR_IDENTIFIER accessGroup:nil];
[keychain setObject:(__bridge id)kSecAttrAccessibleWhenUnlocked forKey:(__bridge id)kSecAttrAccessible];

[keychain setObject:userName forKey:(__bridge id)kSecAttrAccount];
[keychain setObject:[password dataUsingEncoding:NSUTF8StringEncoding] forKey:(__bridge id)kSecValueData];
于 2014-05-22T21:32:14.210 回答