TouchID 登陆

点击这里:KeyChain and TouchID Demo

首先需要引入一个库#import <LocalAuthentication/LAContext.h>,官方的一个用于TouchID安全验证登陆的Framework,首先需要验证是否支持TouchID登陆:

1
2
3
4
5
6
7
// 判断是否支持TouchID登陆
- (BOOL)canEvaluatePolicy
{
LAContext *context = [[LAContext alloc] init];
NSError *error;
return [context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&error];
}

如果不支持的话,就需要走自己APP中得密码验证方法来进行验证登陆;
如果支持的话,就调起TouchID验证的页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)evaluatePolicy
{
LAContext *context = [[LAContext alloc] init];
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:@"使用TouchID进入APP首页" reply:^(BOOL success, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (success) {
// ...
}
else {
// ...
}
});
}];
}

注意

reply这个block中得代码是在异步线程中执行的,如果有需要的话,必须得抛到主线程中去执行一些东西。

TouchID的失败次数是有限制的,从开始算,如果失败了三次,系统就会自动的退出TouchID的验证界面,并在replyblock中返回失败的信息,这时还可以调起TouchID的验证,此时如果失败两次即会退出验证界面。
在这种情况下,如果还想用TouchID验证,则必须输入手机的登陆密码才能继续验证。
需要注意的是,这个失败的次数在iOS设备中所有的APP是累加起来的。

KeyChain

keychain是作为苹果中对于数据安全存储的一个地方,类似于NSUserDefaults,但又不同于NSUserDefaults

对于keychain来说,需要注意的方法有以下四个:

  • SecItemAdd
  • SecItemUpdate
  • SecItemCopyMatching
  • SecItemDelete

注意

在使用的时候一定要注意KeyChain操作的返回值。

KeyChain操作中得一些参数的设定(一些相关参数的说明):

1
2
3
4
5
6
7
8
9
10
11
12
13
- (NSMutableDictionary *)getKeychainQueryWithService:(NSString *)service
{
NSMutableDictionary *query = [NSMutableDictionary dictionaryWithObjectsAndKeys:
(__bridge id)kSecClassGenericPassword, (__bridge id)kSecClass,
service, (__bridge id)kSecAttrService,
service, (__bridge id)kSecAttrAccount,
(__bridge id)kSecAttrAccessibleAfterFirstUnlock, (__bridge id)kSecAttrAccessible,
nil];
if (self.accessGroupName != nil) {
[query setObject:self.accessGroupName forKey:(__bridge id)kSecAttrAccessGroup];
}
return query;
}

SecItemAdd

用于将个人数据条目保存到keychain中。

  • 方法原型:OSStatus SecItemAdd(CFDictionaryRef attributes, CFTypeRef * result);
    • attributes : 添加属性的设置。
    • result : 可以用与获取返回值,&res
1
2
3
4
5
6
- (OSStatus)saveObject:(id)object forService:(NSString *)service {
NSMutableDictionary *keychainQuery = [self getKeychainQueryWithService:service];
// 需要检查数据的存在性,如果存在的话,就用SecItemUpdate更新,否则就添加
[keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:object] forKey:(__bridge id)kSecValueData];
return SecItemAdd((__bridge CFDictionaryRef)keychainQuery, NULL);
}

SecItemUpdate

用于更新KeyChain中保存的数据。

  • 方法原型:OSStatus SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate);
    • query : 查找属性的设置
    • attributesToUpdate : 要更新的数据
1
2
3
4
5
6
7
8
- (OSStatus)updateService:(NSString *)service withObject:(id)object
{
NSDictionary *keychainQuery = [self getKeychainQueryWithService:service];
NSDictionary *changes = @{
(__bridge id)kSecValueData : [NSKeyedArchiver archivedDataWithRootObject:object]
};
return SecItemUpdate((__bridge CFDictionaryRef)keychainQuery, (__bridge CFDictionaryRef)changes);
}

SecItemCopyMatching

用于获取保存在KeyChain中得数据。

  • 方法原型:OSStatus SecItemCopyMatching(CFDictionaryRef query, CFTypeRef * result);
    • query : 保存在KeyChain中得数据信息。
    • result : 从KeyChain中取得的数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (id)loadObjectForService:(NSString *)service {
NSMutableDictionary *keychainQuery = [self getKeychainQueryWithService:service];
[keychainQuery setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
[keychainQuery setObject:(__bridge id)kSecMatchLimitAll forKey:(__bridge id)kSecMatchLimit];
CFDataRef dataRef = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)keychainQuery, (CFTypeRef *)&dataRef);
id results = nil;
if (status == errSecSuccess) {
@try {
results = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)dataRef];
}
@catch (NSException *exception) {
NSLog(@"%@", exception);
}
@finally {
NSLog(@"finally");
}
}
if (dataRef) {
CFRelease(dataRef);
}
return results;
}

SecItemDelete

用于删除保存在KeyChain中得数据。

  • 方法原型:OSStatus SecItemDelete(CFDictionaryRef query)
    • query:要删除的数据的信息。
1
2
3
4
- (void)delete:(NSString *)service {
NSMutableDictionary *keychainQuery = [self getKeychainQueryWithService:service];
SecItemDelete((__bridge CFDictionaryRef)keychainQuery);
}

TIDLogin

附:

OSStatus状态码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CF_ENUM(OSStatus)
{
errSecSuccess = 0, /* No error. */
errSecUnimplemented = -4, /* Function or operation not implemented. */
errSecIO = -36, /*I/O error (bummers)*/
errSecOpWr = -49, /*file already open with with write permission*/
errSecParam = -50, /* One or more parameters passed to a function where not valid. */
errSecAllocate = -108, /* Failed to allocate memory. */
errSecUserCanceled = -128, /* User canceled the operation. */
errSecBadReq = -909, /* Bad parameter or invalid state for operation. */
errSecInternalComponent = -2070,
errSecNotAvailable = -25291, /* No keychain is available. You may need to restart your computer. */
errSecDuplicateItem = -25299, /* The specified item already exists in the keychain. */
errSecItemNotFound = -25300, /* The specified item could not be found in the keychain. */
errSecInteractionNotAllowed = -25308, /* User interaction is not allowed. */
errSecDecode = -26275, /* Unable to decode the provided data. */
errSecAuthFailed = -25293, /* The user name or passphrase you entered is not correct. */
};