如何在iOS设备上卸载应用程序后保留identifierForVendor?

73

我正在开发一款iOS应用程序,它调用Web服务进行登录,同时我将登录凭据和供应商标识符(identifierForVendor)发送到Web服务器,以唯一地标识设备,从而为这些凭据提供保护。因此,用户只能使用一个设备和一个凭证。

我使用以下方法获取identifierForVendor:

NSString *uuid = [[UIDevice currentDevice] identifierForVendor].UUIDString

此标识符将存储在Web服务器和设备数据库中。当用户下次打开应用程序并尝试从Web服务器下载数据时,首先将比较用户设备上的本地identifierForVendor与存储在Web服务器上的标识符。

问题出现在用户卸载应用程序并重新安装时,我发现identifierForVendor已更改。因此,用户无法进一步进行操作。

我阅读了苹果文档UIDevice文档

如上所述,如果同一供应商的所有应用程序都从设备中卸载,则在该供应商的任何应用程序的新安装时将采用新的identifierForVendor。

那么,在我的情况下如何处理?


2
我不知道这样做是否可行,但是把它保存在Keychain中怎么样?你可以在启动时检查此标识符是否在KeyChain中,如果不存在,则获取一个并将其存储在Keychain中。 - Larme
2
嗨Gekb,你找到解决方案了吗?我也遇到了同样的情况。 - Kirti Nikam
8个回答

66
你可以将它保存在KeyChain中。
-(NSString *)getUniqueDeviceIdentifierAsString
{

 NSString *appName=[[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleNameKey];

 NSString *strApplicationUUID = [SSKeychain passwordForService:appName account:@"incoding"];
 if (strApplicationUUID == nil)
 {
    strApplicationUUID  = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
    [SSKeychain setPassword:strApplicationUUID forService:appName account:@"incoding"];
 }

 return strApplicationUUID;
}

11
如果启用了iCloud钥匙串同步,这会起作用吗? - Kristof Van Landschoot
7
当钥匙串同步时,这种方法不起作用,所有已同步的设备将获得相同的厂商 ID。 - rckoenes
8
我会将此内容翻译为:“由于上述同步问题,我只是打算投反对票。” - Jonny
@KristofVanLandschoot 要使其与iCloud配合使用,您需要在iCloud中使用钥匙串:https://dev59.com/eWgu5IYBdhLWcg3wOUgk - loretoparisi
1
请注意钥匙串,目前钥匙串中的项目可以在应用卸载和重新安装周期中保留,但这种情况可能会在未来发生变化。在iOS 10.3 beta 3中,已经删除了钥匙串项目,但在最终版本中又被还原。了解更多信息,请访问 https://dev59.com/22Mk5IYBdhLWcg3w2RaT 。 - Andreas Paulsson
显示剩余10条评论

19

通常不要使用identifierForVendor。相反,使用NSUUID生成自定义UUID并将其存储在钥匙串中(因为如果应用程序被删除并重新安装,则不会删除钥匙串)。


在不同设备上的相同应用程序的不同版本中,identifierForVendor 是否有可能返回重复内容? - PrasadW
我会期望是的,但机率微乎其微。 - Wain
1
没有重复的机会。UUID不是随机的,它们是经过计算的,而UUID的一部分是设备ID。 - Marcus Adams
4
NSUUID创建的UUID符合RFC 4122版本4,并使用随机字节创建。但重复的概率仍然非常小,正如其他人所说:“在未来100年中每秒生成10亿个UUID后,产生一个重复UUID的概率仅为50%。”-https://dev59.com/M3M_5IYBdhLWcg3w9oMA#1155027。 - mcfedr
使用identifierForVendor是一个不错的选择,但是它不应该再存储在keychain中了。https://dev59.com/snA65IYBdhLWcg3wuRB8#48405739 自iOS 10.3以来,它已经不再是持久化的了。 - ForceMagic
显示剩余2条评论

10

除了 @nerowolfe 的答案,还有以下补充:

SSKeychain 默认使用 kSecAttrSynchronizableAny 作为同步模式。您可能不希望 identifierForVendor 在多个设备之间同步,因此这是一个代码示例:

// save identifierForVendor in keychain without sync
NSError *error = nil;
SSKeychainQuery *query = [[SSKeychainQuery alloc] init];
query.service = @"your_service";
query.account = @"your_account";
query.password = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
query.synchronizationMode = SSKeychainQuerySynchronizationModeNo;
[query save:&error];

1
从Cocoapods:SSKeychain已被弃用,推荐使用SAMKeychain。 - crawler

5

您可以尝试使用KeyChain来保存您的VendorIdentifier,即使您卸载应用程序,该标识符也会在设备重置之前一直存在。


很不幸,最近它们进行了更改,KeyChain 不再安全地存储持久数据。https://dev59.com/snA65IYBdhLWcg3wuRB8#48405739 - ForceMagic
@ForceMagic 感谢更新,我的回答已经三年了,我会找另一种方法。所有问题的答案仍然存储在Keychain中,它们没有更新。所以将其降低评分是毫无意义的,因为当问题被提出时,这个方法是可行的。 - iphonic
是的,我确实明白大部分答案都已经过时了。我认为负投票更像是“这不再正确”而不是“这是一个糟糕的答案”。但也许我错了。 - ForceMagic
@ForceMagic,答案仍然正确,请参见此链接https://dev59.com/22Mk5IYBdhLWcg3w2RaT#18944600和此链接https://dev59.com/h5_ha4cB1Zd3GeqPy3N3#43063683。 - iphonic
从你的链接中可以看到:“在苹果文档中建议我们不要依赖于在应用程序卸载后钥匙串访问数据保持完好无损,这种情况即将改变。” - ForceMagic
@ForceMagic 这意味着我的答案仍然有效,虽然不应该依赖于它,当然我个人从未使用过Keychain,但只要它能够解决某些问题并且有用,我们可以使用它,即使它不是最佳解决方案,我应该得到点赞,因为这个答案仍然有效。谢谢。 - iphonic

4

好的。我不想使用第三方库 - 即SSKeychain。所以这是我尝试的代码,相当简单并且运行良好:

    NSString *bundleId = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIdentifier"];

KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc] initWithIdentifier:bundleId accessGroup:nil];
if(![keychainItem objectForKey:(__bridge id)(kSecValueData)]){
    NSString *idfa = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
    [keychainItem setObject:idfa forKey:(__bridge id)(kSecValueData)];
    NSLog(@"saving item %@", [keychainItem objectForKey:(__bridge id)(kSecValueData)]);
}else{
    NSLog(@"saved item is %@", [keychainItem objectForKey:(__bridge id)(kSecValueData)]);
}

+1 这是一个非常简单的解决方案,结合iCloud钥匙串同步(使用属性kSecAttrSynchronizable),成为了这个问题的最佳解决方案。 - loretoparisi
1
如果您使用kSecAttrSynchronizable,那么您在所有设备上都会有相同的值吗?@loretoparisi - jcesarmobile
@jcesarmobile 没错,如果你在 @gautam-jain 的代码中添加 kSecAttrSynchronizable,你就可以在iCloud应用程序属性中找到它,并将其同步到所有设备上。 - loretoparisi
如果您希望每个设备具有不同的值,那么这并不是一个好主意。 - jcesarmobile
是的,根据您的需求,您可以保留设备标识符、设备供应商或两者都像属性identifierForVendoradvertisingIdentifierASIdentifierManager - loretoparisi

3

Swift版本

func UUID() -> String {

    let bundleName = NSBundle.mainBundle().infoDictionary!["CFBundleName"] as! String
    let accountName = "incoding"

    var applicationUUID = SAMKeychain.passwordForService(bundleName, account: accountName)

    if applicationUUID == nil {

        applicationUUID = UIDevice.currentDevice().identifierForVendor!.UUIDString

        // Save applicationUUID in keychain without synchronization
        let query = SAMKeychainQuery()
        query.service = bundleName
        query.account = accountName
        query.password = applicationUUID
        query.synchronizationMode = SAMKeychainQuerySynchronizationMode.No

        do {
            try query.save()
        } catch let error as NSError {
            print("SAMKeychainQuery Exception: \(error)")
        }
    }

    return applicationUUID
}

3
我不会称之为Swift版本,因为它需要第三方对象。 - blkbam

2
目前没有确定的方法来将一个唯一的数字与设备关联起来,因为这违反了苹果的隐私指南。您可以尝试在钥匙串中保存自己的唯一标识符,但如果用户清除设备,则此标识符也会消失。通常,将设备与用户链接起来是错误的,因为您不再识别用户而是设备。因此,您应该更改API,以便用户可以重新登录,并且供应商ID绑定到用户帐户。此外,当用户有多个设备(如iPhone和iPad)并在两者上都使用您的应用时,会发生什么情况?由于您的身份验证基于唯一标识符,因此无法完成此操作。

@DouglasHeld 我明白,但是苹果公司已经让这件事情变得不可能了,我试图解释为什么你不应该这样做。 - rckoenes
@rckoenes 如果您需要硬件绑定,您需要识别设备。否则,您如何阻止用户在多个设备上安装它?一个人付款一次,然后他的所有朋友都可以使用他的登录凭据免费获得它!这似乎对开发人员不合理。 - Ash
2
@ash 我明白你的意思,但是苹果不允许这样做。每个苹果账户只支持5台设备,限制这一点是违反苹果政策的。 - rckoenes

1
我曾经使用 KeychainAccess pod 来解决这个问题。
在你的 pod 文件中:
pod 'KeychainAccess', '~> 2.4' //If you are using Swift 2.3 
pod 'KeychainAccess' //Defaults to 3.0.1 which is in Swift 3

在您想要将UUID设置到钥匙串中的文件中导入KeychainAccess模块。
import KeychainAccess

使用以下代码从钥匙串中设置和获取UUID:

注意:BundleId是键,UUID是值

var bundleID = NSBundle.mainBundle().bundleIdentifier
    var uuidValue = UIDevice.currentDevice().identifierForVendor!.UUIDString

 //MARK: - setVenderId and getVenderId
    func setVenderId() {

        let keychain = Keychain(service: bundleID!)

        do {
            try keychain.set(venderId as String, key: bundleID!)
            print("venderId set : key \(bundleID) and value: \(venderId)")
        }
        catch let error {
            print("Could not save data in Keychain : \(error)")
        }
    }

    func getVenderId() -> String {
        let keychain = Keychain(service: bundleID!)
        let token : String = try! keychain.get(bundleID!)!
        return token
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接