LocalAuthentication开发实践

来源:http://www.sh-fengwen.com 作者:家常菜谱 人气:153 发布时间:2019-09-05
摘要:在iPhone 5s加入TouchID后,指纹识别的功能在App中逐渐受到青睐,特别是对于本地安全较高的应用指纹识别是必备的功能,它既能解决在验证过程中输入密码的繁琐过程,同时指纹识的安全

在iPhone 5s加入Touch ID后,指纹识别的功能在App中逐渐受到青睐,特别是对于本地安全较高的应用指纹识别是必备的功能,它既能解决在验证过程中输入密码的繁琐过程,同时指纹识的安全等级更高。那么,要想在自己开发的应用中使用指纹识别,就必须要LocalAuthentication.framework提供的API,下面将详细地介绍如何使用这个框架来实现指纹识别功能。

移动开发实践及‘坑’总结,开发实践总结

又一次懒癌发作,好久没有更新博客了。

做过很多移动端的项目,在开发调试过程中,一款好的调试工具会让效率大大提高。博主之前已经推荐了一款神器:

 

下面,就总结一下移动端遇见的坑。

 

1.input   placeholder问题   在chrome 模拟移动端调试时[左边图],显示的非常正常,但是在真机上[右边图],placeholder里面的内容明显靠上,非常的不美观

图片 1    图片 2

在国外网站,对这个属性的兼容性处理,那就是不要设计input的line-height或者设置line-height为normal即可,

试了一下,虽然在谷歌模拟调试里稍微偏上,但是在“真机上”正常垂直居中~

 

我们先来看下面的例子:

2.line-height

line-height经常用于文字居中,不同手机显示效果不一样。什么鬼~ 在chrome模拟器上又是显示得非常完美,但是!Android和IOS又各自‘偏移’了。如果把line-height加1px,iPhone文字就会稍微‘正常显示’,由于我们app的ios用户居多,并且android机型太多,不同机型也会显示不同,所以只能退而求其次了。line-height的兼容问题不太好解决,容器高度越小,显示效果的差距越明显。

解决方案:稍微大一点的高度,最好把line-height设置为高度+1px,两个平台显示都不会太‘奇怪’。

 

LAContext *context = [[LAContext alloc] init]; NSError *error = nil;if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]){ [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:@"输入指纹进行验证" reply:^(BOOL success, NSError * _Nullable error) { if  { NSLog; } else { NSLog; } }];}else{ NSLog(@"识别功能不可用");}

3.使用rem  (兼容性:ie9+)

原理:浏览器的默认字体高都是16px,未经调整的浏览器在显示1em=16px。rem则是只相对于根元素的font-size,即只需要设置根元素的font-size,其它元素使用rem单位设置成相应的百分比即可;

一般使用:设置html的font-size为62.5%

 1 html {
 2     font-size: 62.5%;
 3 }
 4 body {
 5     font-size: 12px;
 6     font-size: 1.2rem;
 7 }
 8 p {
 9     font-size: 14px;
10     font-size: 1.4rem;
11 }

我们来解读一下上面的代码:

4.实现自定义原生控件的样式

由于select移动端原生样式很丑,但是原生弹出样式是符合我们设计的原则

解决方法:将原本select 设置为透明,z-index设置高~再用一个比较好看的样式‘假装’在表面

  • LAContext为贯穿整个识别过程的对象类型。使用识别必须初始化一个LAContext对象。

  • 在进行指纹识别前,需要判断识别功能是否可用,上面代码中的canEvaluatePolicy: error :方法就是做这样一件事情。当方法返回YES时则可以继续调用识别方法。否则需要根据error的描述来提示用户。该方法的policy参数决定了鉴权的行为方式,该参数取值如下:

5.移动端使用innerHtml绘制

使用innerHTML绘制大段,之后想获取HTML的ID节点,事实上是获取不到的,这种问题在动态创建DOM会经常发生

这也是一个神器的问题,博主自己写了一个移动端轮播插件,在chrome上浏览非常正常,但到了真机上却显示空白,各种百度,最后才发现这么坑的地方…

解决方案:尝试了很多方法之后,老老实实在页面直接用html结构,如果有更好的方法,也请告诉我。

 

取值 说明
LAPolicyDeviceOwnerAuthenticationWithBiometrics 指纹识别。验证弹框有两个按钮,第一个是取消按钮,第二个按钮可以自定义标题名称。只有在第一次指纹验证失败后才会出现第二个按钮,这种方式下的第二个按钮功能需要自己定义。前三次指纹验证失败,指纹验证框不再弹出。再次重新进入验证,还有两次验证机会,如果还是验证失败,TOUCH ID 被锁住不再继续弹出指纹验证框。以后的每次验证都将会弹出设备密码输入框直至输入正确的设备密码才能重新使用指纹识别
LAPolicyDeviceOwnerAuthentication 指纹识别或系统密码验证。如果Touch ID 可用,且已经录入指纹,则优先调用指纹验证。其次是调用系统密码验证,如果没有开启设备密码,则不可以使用这种验证方式。指纹识别验证失败三次将弹出设备密码输入框,如果不进行密码输入。再次进来还可以有两次机会验证指纹,如果都失败则Touch ID被锁住,以后每次进来验证都是调用系统的设备密码直至输入正确的设备密码才能重新使用指纹识别

6.300ms延迟

  • 方案一:禁用缩放

在HTML文档头部包含如下meta标签时: <meta name="viewport" content="user-scalable=no"/> <meta name="viewport" content="initial-scale=1,maximum-scale=1"/>   缺点------就是必须通过完全禁用缩放来达到去掉点击延迟的目的,然而完全禁用缩放并不是我们的初衷,我们只是想禁掉默认的双击缩放行为,这样就不用等待300ms来判断当前操作是否是双击。  

  • 方案二:更改默认的视口宽度

<meta name="viewport" content="width=device-width"/> 如果设置了上述meta标签,那浏览器就可以认为该网站已经对移动端做过了适配和优化,就无需双击缩放操作了。
这个方案相比方案一的好处在于,它没有完全禁用缩放,而只是禁用了浏览器默认的双击缩放行为,但用户仍然可以通过双指缩放操作来缩放页面。
  兼容性问题: 对于方案一和方案二,Chrome是率先支持的,Firefox紧随其后,然而令Safari头疼的是,它除了双击缩放还有双击滚动操作,如果采用这种两种方案,那势必连双击滚动也要一起禁用。

 

该方法可以能返回的错码如下所示:

7.点击穿透

问题常见发生场景: 假如页面上有两个元素A和B。B元素在A元素之上。我们在B元素的touchstart事件上注册了一个回调函数,该回调函数的作用是隐藏B元素。我们发现,当我们点击B元素,B元素被隐藏了,随后,A元素触发了click事件。

这是因为在移动端浏览器,事件执行的顺序是touchstart > touchend > click。

而click事件有300ms的延迟,当touchstart事件把B元素隐藏之后,隔了300ms,浏览器触发了click事件,但是此时B元素不见了,所以该事件被派发到了A元素身上。如果A元素是一个链接,那此时页面就会意外地跳转。

解决思路:

1.不要混用touch和click 

2.消耗掉touch之后的click

解决方法:

1.只用touch   把页面内所有click全部换成touch事件( touchstart 、’touchend’、’tap’),注意:a标签的href也是click,需要换成js的跳转。 

2.改动最小——350ms后再隐藏B元素

 

错误码 说明
LAErrorPasscodeNotSet 没有设置设备密码,无法使用指纹识别
LAErrorTouchIDNotAvailable 设备不支持Touch ID/Face ID,iOS 11被标注过时,需要使用LAErrorBiometryNotAvailable代替
LAErrorBiometryNotAvailable 设备不支持Touch ID/Face ID,iOS 11新增,由于新增Face ID,故用来代替LAErrorTouchIDNotAvailable
LAErrorTouchIDNotEnrolled 没有录入指纹/人脸,iOS 11被标注过时,需要使用LAErrorBiometryNotEnrolled代替
LAErrorBiometryNotEnrolled 没有录入指纹/人脸,iOS 11新增,由于新增Face ID,故用来代替LAErrorTouchIDNotEnrolled
LAErrorBiometryLockout 超过重试限制,Touch ID/Face ID被锁定,需要进行设备密码解锁后重新激活

8. 虚拟键盘导致fixed元素错位

fixed元素一定会伴随虚拟键盘的出现,但是虚拟键盘只是“贴”在了viewport上,表面上不会对dom产生“任何”影响,但是这个时候fixed元素表现却变得怪异起来,会错位。

解决原理:虚拟键盘弹出时将fixed元素设置为static,虚拟键盘消失时候设置回来。

解决方案:由于虚拟键盘出现并未抛出事件,而检测scroll或者resize事件,皆会有一定延迟,会出现闪烁现象。则当前获取焦点元素为文本元素,就将fixed元素设置为static。

  • 检测可用后,调用evaluatePolicy:localizedReason:reply:方法来进行指纹识别。其中policy参数应该与调用canEvaluatePolicy: error :方法时传入的policy一致。而localizedReason则是显示在识别标题下面的一栏描述文本,如图所示:

9.移动端手势

手指放在屏幕上:ontouchstart     手指在屏幕上滑动:ontouchmove      手指离开屏幕:ontouchend

原理:

1.在touchstart事件触发时,  记录手指按下的时间startTime,本次滑动的初始位置initialPos。

2.在touchmove事件触发时, 记录当前位置nowPosition(实时移动元素),滑动距离movePosition(当前位置nowPosition与初始位置initialPos的差值),判断正负数再决定是左还是右移动。

3.在touchend事件触发时,   记录手指离开屏幕的时间endTime,获得手指在屏幕上停留的时间(endTime-startTime),滑动距离movePosition

  • 判断是否滑动:

 

10.iphone动态生成html元素click失效

这个也是神奇的坑,找了很久资料,也没有很原理的解释。 解决方法:  为绑定click的元素增加css样式   cursor:pointer;

 

如果这篇博客对你有帮助或者有收获的话,请点击右下角的推荐,谢谢!

 

 

 

 

又一次懒癌发作,好久没有更新博客了。 做过很多移动端的项目,在开发调试过程中,一款好...

图片 3localizedReason显示位置

该方法返回的错误如下所示:

错误码 说明
LAErrorPasscodeNotSet 没有设置设备密码,无法使用指纹识别
LAErrorTouchIDNotAvailable 设备不支持Touch ID/Face ID,iOS 11被标注过时,需要使用LAErrorBiometryNotAvailable代替
LAErrorBiometryNotAvailable 设备不支持Touch ID/Face ID,iOS 11新增,由于新增Face ID,故用来代替LAErrorTouchIDNotAvailable
LAErrorTouchIDNotEnrolled 没有录入指纹/人脸,iOS 11被标注过时,需要使用LAErrorBiometryNotEnrolled代替
LAErrorBiometryNotEnrolled 没有录入指纹/人脸,iOS 11新增,由于新增Face ID,故用来代替LAErrorTouchIDNotEnrolled
LAErrorBiometryLockout 超过重试限制,Touch ID/Face ID被锁定,需要进行设备密码解锁后重新激活
LAErrorAuthenticationFailed 验证失败,指的是指纹不匹配
LAErrorUserCancel 用户点击了取消按钮
LAErrorUserFallback 用户点击了输入密码按钮
LAErrorSystemCancel 系统强制取消,可能由于其他应用进入前台
LAErrorAppCancel 应用调用了LAContextinvalidate方法
LAErrorNotInteractive 应用尚未启动完成或者已经进入非激活状态时调用验证方法会收到该错误,例如:将验证方法放到didEnterBackground方法中进行可能会导致这个错误。

通过上面的例子和解释,大家对LocalAuthentication这个框架应该有了一定的了解吧,但是作为一种验证方式,上面的做法是不够安全和严谨的。举个例子,如果我知道你的设备密码,然后通过密码登录你的手机,然后我在你的设备上登记了我的指纹,那么按照上面代码的逻辑,我的指纹也是能够验证通过的。因此,这里需要借助iOS 9上LAContext的一个新属性evaluatedPolicyDomainState来解决这个问题(没听错,是iOS 9上新增的,也就是说在iOS 8上会存在我说的这种问题,可能苹果爸爸一开始考虑,既然手机设备密码都泄漏了,那么自然手机里面的信息都是不安全的,但是往往App中的验证体系是独立于系统的,某些重要的App密码不泄漏还是很安全的,但是加入了指纹识别后可能就变得不安全了,所以不建议iOS 8上实现指纹识别功能)。

evaluatedPolicyDomainState表示当下Touch ID/Face ID的一个状态,没有其他的含义。当在在设备上添加、删除指纹时,这个值就会发生变化。所以,拿到这个值作比对就能够很容易知道是否有发生变化,下面将继续介绍具体的实现方法。

在写代码前,先来理清楚整个实现的过程,在一般的情况下我们都会给应用做一个开关,用于控制是否开启指纹识别。那么,基于这个前提,我们可以作如下的流程处理:

  1. 设置一个开关(UISwitch
  2. 当开关开启时,要求用户进行指纹识别,在识别成功后将evaluatedPolicyDomainState保存起来,用于后续指纹验证时对比。
  3. 在需要验证的地方,使用指纹识别API进行验证,同时获取evaluatedPolicyDomainState来比对之前保存的值,如果相同则验证成功,否则验证失败,需要进行后续的处理,如需要输入应用账号的密码。
  4. 通过App自身验证体系检测通过后在把新的evaluatedPolicyDomainState保存起来,用于往后的验证操作。

理解上面的流程后,我们来看下面的示例代码:

- switchChangedHandler:sender{ if (self.touchIdSwitch.on) { //开启指纹 LAContext *context = [[LAContext alloc] init]; if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:nil]) { //进行第一次的验证,成功后记录验证状态 [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:@"输入指纹开启验证" reply:^(BOOL success, NSError * _Nullable error) { if  { self.policyDomainState = context.evaluatedPolicyDomainState; } else { NSLog; self.touchIdSwitch.on = NO; } }]; } else { NSLog(@"Touch ID/Face ID不可用"); self.touchIdSwitch.on = NO; } } else { self.policyDomainState = nil; }}- validationButtonClickedHandler:sender{ if (self.touchIdSwitch.on) { LAContext *context = [[LAContext alloc] init]; if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:nil]) { //进行第一次的验证,成功后记录验证状态 [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:@"使用指纹识别验证" reply:^(BOOL success, NSError * _Nullable error) { if  { if ([context.evaluatedPolicyDomainState isEqualToData:self.policyDomainState]) { NSLog; } else { NSLog(@"指纹发生变化,请进行后续验证步骤"); //这里可以弹出App的登录界面让用户重新登录 //等待登录完成后再将evaluatedPolicyDomainState保存起用于后续的操作 [self doAppAuthentication:^(BOOL success) { if  { self.policyDomainState = context.evaluatedPolicyDomainState; NSLog; } }]; } } else { NSLog; } }]; } else { NSLog(@"Touch ID/Face ID不可用"); } }}- doAppAuthentication:(BOOL success))handler{ //执行应用自身验证体系,并将验证结果回调 if  { handler ; }}

整个示例的界面如下所示:

图片 4示例运行效果

代码中的switchChangedHandler:方法为点击界面中的开关按钮的触发事件。可以看到当触发该事件时,而且开关处于打开状态,就要进行一次指纹验证,只有当指纹识别通过时才视为真正启用指纹识别功能,然后把evaluatedPolicyDomainState保存起来(为了方便演示,示例中只在内存中保留,原则上要将该值进行本地保存)。

然后当点击示例中的“验证指纹”按钮时就会触发validationButtonClickedHandler方法,这个时候会调起指纹验证。在验证回调中如果successYES并且evaluatedPolicyDomainState与之前记录的值相同时才算是验证通过。如果仅仅是successYES那么就要进行进一步的身份验证,示例中就会调起doAppAuthentication方法。在该方法中进行App相关的验证方法,例如让用户重新使用账号密码进行登录等一系列鉴定用户的操作。那么,为了方便演示,示例中直接认为App验证用户身份成功,在App验证成功后要做的一步操作就是将原来保存的evaluatedPolicyDomainState替换成新的,后续就基于这个指纹状态来校验用户身份了。(注:我们没有办法知道指纹的删除和录入是否是设备持有人的操作,所以基于安全的角度考虑,只要有修改就应该进行应用自身的验证操作来确保一些恶意行为)。

上面所说的是一个相对完整的实践过程,可能会存在一些理解有误的地方,如果发现了希望同学们给予指出。讲到这里,LocalAuthentication框架的内容并没有全部结束,还有一些其他的内容,下面进行一一讲解。

Face ID其实就是人脸识别中对人脸的唯一标识。苹果目前在iPhone X设备中应用了这项技术,通过人脸识别来解锁设备。同样如果App内需要使用人脸识别来解锁某些访问,也是使用LAContext来实现,而且实现流程跟Touch ID一样,几乎不需要改写任何代码。唯一需要注意的地方是,iOS 11后LAContext新增一个只读属性biometryType,该属性表示当前设备支持生物识别类型(是Touch ID还是Face ID),其枚举值说明如下:

枚举值 说明
LABiometryTypeNone 表示设备不支持生物识别技术
LABiometryNone 在iOS 11中已经过时,使用LABiometryTypeNone代替
LABiometryTypeTouchID 表示当前设备支持指纹识别
LABiometryTypeFaceID 表示当前设备支持人脸识别

那么,这个值最重要的作用就是让你可以区分到底现在使用的是Touch ID还是Face ID,然后编码时可以根据类型来作不同的提示和判断。例如:

NSString *reason = @"使用指纹识别验证";if (@available(iOS 11.0, *)){ if (context.biometryType == LABiometryTypeFaceID) { reason = @"使用人脸识别验证"; }} [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:reason reply:^(BOOL success, NSError * _Nullable error) { //Do something...}];

注意:使用Face ID功能必须要在Info.plist中加入NSFaceIDUsageDescription并添加准确的使用描述。

在iOS 9之前我们写入到Keychain的数据,在设备解锁后就能够对keychain中的数据进行访问,其实这样是不够安全的,特别是在你的App使用第三方SDK的情况底下,很有可能就会去窃取App中的keychain数据。那么,在iOS 9之后,系统加入了一项新的功能,就是允许应用来控制Keychain的数据访问。它的实现过程是在写入数据时添加一个应用级别的访问密码,后续要访问这个数据除了要设备解锁,还需要有正确的密码才能够访问Keychain中的数据。而这功能正好是集成到了LocalAuthentication这个框架中,下面我们来探索一下这个功能的用法。

要实现Keychain的控制访问,依然还是要依靠LAContext来实现,我们可以看到在iOS 9之后,这个类型新增了一个方法setCredential:type:。这个方法的作用就是把访问Keychain的密码设置到LAContext对象中,配合LACredentialTypeApplicationPassword这个类型就能够实现控制访问了,下面先来看一下实现的示例代码:

OSStatus status = noErr;CFErrorRef error = NULL;SecAccessControlRef acl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleAfterFirstUnlock, kSecAccessControlApplicationPassword, &error);if { NSString *data = @"要写入的数据"; LAContext *context = [[LAContext alloc] init]; NSData *password = [@"123456" dataUsingEncoding:NSUTF8StringEncoding]; [context setCredential:password type:LACredentialTypeApplicationPassword]; NSDictionary *saveDictionary = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, (__bridge id)kSecAttrService: @"testService", (__bridge id)kSecAttrAccount: @"testAccount", (__bridge id)kSecValueData: [data dataUsingEncoding:NSUTF8StringEncoding], (__bridge id)kSecAttrAccessControl: (__bridge id)acl, (__bridge id)kSecUseAuthenticationContext:context }; status = SecItemAdd((__bridge CFDictionaryRef)saveDictionary, nil); if (status == noErr) { NSLog(@"Item stored to keychain"); } CFRelease;}

从上面写入Keychain信息的代码可以看到,在创建访问控制权限时使用的是kSecAccessControlApplicationPassword枚举值,这是iOS 9新增的选项,表示你的Keychain数据访问要使用应用密码。同时也可以看到保存的Keychain数据结构中多了一项kSecUseAuthenticationContext,这也是iOS 9新增的,它对应的值就是setCredential后的LAContext对象。

通过这样的操作,Keychain数据就能够实现应用密码访问的功能了。下面我们再来看看如何读取这样类型的数据:

本文由美高梅游戏平台网站发布于家常菜谱,转载请注明出处:LocalAuthentication开发实践

关键词:

上一篇:Python循环语句中whlie和for的行使

下一篇:没有了

最火资讯