前段时间介绍过DeviceID安全的获取方式, 保存DeviceID在系统升级后都能正常获取的重点就是KeyChain,在iOS 10.3的beta版本中,apple对KeyChain进行的改动,哪怕现在KeyChain能正常获取,我们也要想一些办法升级下deviceID的存取。
/** 升级后获取设备标识符
safariCookie -> cacheManager -> iCloud -> IDFA -> IDFV -> pushToken
iCloud支持需开启Key-value storage
*/
+ (NSString *)deviceId;
具体的实现伪代码
NSString *const kMFSIdentifierCacheDeviceIdKey = @"com.imfong.Device.cacheKey";
NSString *const kMFSIdentifierUserDeviceIdKey = @"com.imfong.deviceId.defaultkey";
NSString *const kMFSIdentifierKeyChainKey = @"com.imfong.deviceId.KeyChain.Key";
+ (NSString *)deviceId
{
NSString *cacheDeviceId = [cacheManager() objectForKey:kMFSIdentifierCacheDeviceIdKey];
NSString *userDeviceId = [userDefaults() objectForKey:kMFSIdentifierUserDeviceIdKey];
//缓存和UserDefaults都是存储在本地,数据存在说明已经非首次安装,数据相同说明是正常用户
if (cacheDeviceId.length && userDeviceId.length && [cacheDeviceId isEqualToString:userDeviceId]) {
NSString *cookieDId = [MFSSafariCookieDeviceId cookieDId]; //基于缓存存储,开启APP时肯定返回nil
//cookieDID存在,则以cookieDID为准(cookieDID值会进行AES解密,理论上安全可信)
if (cookieDId.length && ![cookieDId isEqualToString:cacheDeviceId]) {
return cookieDId;
}
return cacheDeviceId;
}
else {
NSString *keyChain_Key = kMFSIdentifierKeyChainKey;
NSString *deviceId = [MFSKeyChain objectForKey:keyChain_Key];
if (!deviceId.length) {
//SafariCookieDeviceId首次获取必定是nil,写在此处是为了容错
deviceId = [MFSSafariCookieDeviceId cookieDId];
if (!deviceId.length) {
//走到这步说明UserDefaults的plist被人为删除,获取客户端内缓存数据
deviceId = cacheDeviceId;
if (!deviceId.length) {
//客户端内缓存数据也被清理,获取iCloud数据
deviceId = [[NSUbiquitousKeyValueStore defaultStore] objectForKey: kMFSIdentifierUserDeviceIdKey];
if (!deviceId.length) {
//iCloud未登陆或者被清空,获取IDFA
if ([[ASIdentifierManager sharedManager] isAdvertisingTrackingEnabled]) {
deviceId = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
}
if (!deviceId.length) {
//IDFA被用户手动重置,取IDFV
deviceId = [[UIDevice currentDevice] identifierForVendor].UUIDString;
if (!deviceId.length) {
//什么都取不到,获取token
deviceId = @"devicePushToken值";
if (!deviceId.length) {
deviceId = [[NSUUID UUID] UUIDString];
}
}
}
}
}
}
}
if (deviceId.length) {
[MFSKeyChain setObject:deviceId forKey:keyChain_Key];
[cacheManager() setObject:deviceId forKey:kMFSIdentifierCacheDeviceIdKey duration:-9];
[userDefaults() setObject:deviceId forKey:kMFSIdentifierUserDeviceIdKey];
[[NSUbiquitousKeyValueStore defaultStore] setString:deviceId forKey: kMFSIdentifierUserDeviceIdKey];
[[NSUbiquitousKeyValueStore defaultStore] synchronize];
}
return deviceId ?: @"";
}
}
#pragma mark -
static MFSCacheManager *cacheManager() {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[MFSCacheManager alloc] initWithSuiteName:@"com.imfong.Identifier.Cache"];
});
return instance;
}
static NSUserDefaults *userDefaults() {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[NSUserDefaults alloc] initWithSuiteName:@"com.imfong.Identifier.userDefaults"];
});
return instance;
}
SafariCookie的获取和存储伪代码:
@implementation MFSSafariCookieDeviceId
+ (void)setCookieDId:(NSString *)idd {
if (idd.length) {
//传出去的是AES加密值,理论上安全
NSString *deviceId = [[idd AESEncryptAndBase64Encode] URLEncodedString];
NSString *url = [NSString stringWithFormat:@"%@?value=%@", @"https://www.imfong.com/app/device", deviceId];
//通过SFSafariViewController打开链接(使用addChildViewController的形式并设置view.frame为{0, 0, 1, 1})并通过JS唤起app
//window.location.href = "mfsApp://imfong/app/device?deviceId="+(value的值);
}
}
+ (NSString *)cookieDId {
return [cacheManager() objectForKey:@"value"];
}
@end
只剩下最后一次,app的scheme内的处理
- (void)scheme的拦截后的deviceID处理方法:(NSString *)value {
if (系统版本大于iOS9(SFSafariViewController iOS9开始支持)) {
//浏览器传回来的值解密,能正确解密则表示数据有效
NSString *deviceId = [[value URLDecodedString] AESDecryptAndBase64Decode];
if (!deviceId.length) {
//此处触发一次cookie存储
//scheme为异步形式,客户端内其他时机无法直接触发
deviceId = [MFSIdentifier deviceId];
[MFSSafariCookieDeviceId setCookieDId:deviceId];
}
//存到MFSSafariCookieDeviceId供cookieDId使用
[cacheManager() setObject:deviceId forKey:@"value" duration:-1];
//已安装应用的设备在+deviceid方法中,不会触发NSUbiquitousKeyValueStore,所以需要在另外一个地方触发存储
[[NSUbiquitousKeyValueStore defaultStore] setString:deviceId forKey:@"com.imfong.deviceId.key...."];
[[NSUbiquitousKeyValueStore defaultStore] synchronize];
}
}
其他
大概的流程就是这样,细节部分有疑惑的可以再探讨。
比较重要的就是加密解密部分,如果加密不靠谱或者已经泄露,deviceid可能会被随意改动。
多个app通过cookie能保持deviceid共享,获取到唯一deviceid。