app里的登錄模塊就是保存登錄過(guò)用戶名和密碼捅位,如果用戶選擇記住密碼則保存最長(zhǎng)保存7天,這樣用戶下次登錄app的時(shí)候搂抒,就可以直接登錄了艇搀,或者輸入已經(jīng)登錄過(guò)的帳號(hào)時(shí),自動(dòng)填充密碼求晶。之前由于時(shí)間趕焰雕,用的NSUserDefaults 進(jìn)行保存的用戶名,密碼芳杏,時(shí)間明文保存的矩屁,估計(jì)這個(gè)讓老板知道會(huì)打死我,其實(shí)自己想想也覺(jué)得后背發(fā)涼爵赵。今天剛好有時(shí)間發(fā)現(xiàn)Keychain正好可以滿足這個(gè)需求吝秕。
Keychain是一塊相對(duì)獨(dú)立的空間,當(dāng)app升級(jí)時(shí)亚再,已保存在Keychain里的信息不會(huì)被刪除掉郭膛。Keychain的數(shù)據(jù)即使被別人拿到也由于是加密的,無(wú)法看到Keychain里的數(shù)據(jù)氛悬。因此比較適合用來(lái)存儲(chǔ)用戶的一些機(jī)密信息则剃。
Keychain里主要方法有四個(gè):
OSStatus SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result)
OSStatus SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result)
OSStatus SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate)
OSStatus SecItemDelete(CFDictionaryRef query)
SecItemAdd表示往Keychain 里增加一條數(shù)據(jù),第一個(gè)參數(shù)表示數(shù)據(jù)如捅,第二個(gè)參數(shù)表示執(zhí)行該操作后棍现,指向剛添加的這個(gè)數(shù)據(jù)的引用,如果不需要用到這條數(shù)據(jù)镜遣,可以傳入NULL(翻譯自apple文檔)己肮。
SecItemCopyMatching表示查詢Keychain里是否有符號(hào)條件的記錄士袄。第一個(gè)參數(shù)查詢條件,第二個(gè)查詢到結(jié)果的引用谎僻。
SecItemUpdate更新Keychain里的記錄娄柳。第一個(gè)參數(shù)表示查詢條件,第二個(gè)表示當(dāng)根據(jù)第一個(gè)查詢條件后艘绍,用于更新的值赤拒。
SecItemDelete刪除符號(hào)查詢條件的記錄。參數(shù)表示查詢條件
總結(jié)就是增诱鞠,查挎挖,改,刪航夺。
結(jié)合代碼說(shuō)明怎么用比較好(實(shí)現(xiàn)代碼模仿網(wǎng)上的一個(gè)做法)蕉朵。
QPKeychain.h
@interface QPKeychain :NSObject
+ (void)save:(NSString*)key data:(id)data;
+ (id)load:(NSString*)key;
+ (void)remove:(NSString*)key;
@end
QPKeychain.m文件
#import "QPKeychain.h"
#import@implementation QPKeychain
+ (NSMutableDictionary *)getKeychainQuery:(NSString *)key {
return [NSMutableDictionary dictionaryWithObjectsAndKeys:(__bridge id)kSecClassGenericPassword, (__bridge id)kSecClass,
key, (__bridge id)kSecAttrService, key, (__bridge id)kSecAttrAccount, (__bridge id)kSecAttrAccessibleAfterFirstUnlock,(__bridge id)kSecAttrAccessible, nil];
}
+ (void)save:(NSString *)key data:(id)data {
NSMutableDictionary *keychainQuery = [self getKeychainQuery:key];
SecItemDelete((__bridge CFDictionaryRef)keychainQuery);
[keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge id)kSecValueData];
SecItemAdd((__bridge CFDictionaryRef)keychainQuery, NULL);
}
+ (id)load:(NSString *)key {
id ret = nil;
NSMutableDictionary *keychainQuery = [self getKeychainQuery:key];
[keychainQuery setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
[keychainQuery setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
CFDataRef keyData = NULL;
if (SecItemCopyMatching((__bridge CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
@try {
ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];
} @catch (NSException *e) {
// NSLog(@"Unarchive of %@ failed: %@", key, e);
} @finally {
}
}
if (keyData) {
CFRelease(keyData);
}
return ret;
}
+ (void)remove:(NSString *)key {
NSMutableDictionary *keychainQuery = [self getKeychainQuery:key];
SecItemDelete((__bridge CFDictionaryRef)keychainQuery);
}
@end
實(shí)際上就是3個(gè)功能,保存阳掐,讀取始衅,刪除keychain。
(NSMutableDictionary)getKeychainQuery:(NSString)key方法生成一個(gè)字典锚烦,該字典包含了一條keychain item的設(shè)置觅闽。kSecClass 表示該條item是什么類型的,kSecClassGenericPassword表示一般密碼類型的item(還有幾個(gè)其他類型的涮俄,猜測(cè)這些類型會(huì)采用不同的加密級(jí)別)。kSecAttrService用于描述該item的service屬性(具體用于干什么尸闸,還不太清楚)彻亲。kSecAttrAccount指定item的account name。kSecAttrAccessible顧名思義就是設(shè)置item的屬性訪問(wèn)方式的吮廉,kSecAttrAccessibleAfterFirstUnlock表示只要設(shè)備一旦設(shè)備啟動(dòng)解鎖后就可以一直訪問(wèn)苞尝。
(void)save:(NSString*)key data:(id)data方法將鍵值key,數(shù)據(jù)data存放如item中宦芦。先將keychain里的account name為key的刪除掉宙址,然后再插入(這2步等價(jià)于SecItemUpdate)
(id)load:(NSString*)key方法查找account name 為key的item,然后將查到的item返回调卑。其中用到了SecItemCopyMatching函數(shù)抡砂。
(void)remove:(NSString*)key方法將account name 的item刪除掉。
這樣對(duì)于那個(gè)保存用戶名恬涧,密碼的功能就可以很簡(jiǎn)單的實(shí)現(xiàn)了注益。將用戶名,密碼溯捆,有效的登錄時(shí)間存入一個(gè)字典里丑搔,然后以用戶名為account name,字典為data存入到keychain中。子UITextField的valueDidChanged,textFieldShouldReturn回調(diào)中以用戶當(dāng)前的輸入去獲取密碼來(lái)填充密碼輸入框啤月,代碼如下:
+ (void)fillPasswordTFWithUserNameTF:(UITextField *)userNameTF withPasswordTF:(UITextField *)passwordTF {
NSString *account = userNameTF.text;
NSMutableDictionary *userInfos = [QPKeychain load:account];//獲取keychain里的用戶煮仇,密碼等信息
if (userInfos == nil) {
passwordTF.text = @"";
} else {
double now = [[[NSDate alloc] init] timeIntervalSince1970];
double time = [[userInfos objectForKey:LOGIN_USER_DATE] doubleValue];
if ((now - time) / (24 * 3600) < 7) {//7天有效
NSString *password = [userInfos objectForKey:LOGIN_USER_PASSWORD];
passwordTF.text = password;
} else {
passwordTF.text = @"";
}
}
}
由于水平有限,也沒(méi)時(shí)間去深入研究keychain的具體實(shí)現(xiàn)谎仲,只能點(diǎn)到為止了欺抗。