写点什么

如何在复杂业务场景中优雅实现 Android 指纹验证?,rust 移动端跨平台开发

用户头像
Android架构
关注
发布于: 刚刚

public void onAuthenticationError(int errMsgId, CharSequence errString) {//验证过程中遇到不可恢复的错误 super.onAuthenticationError(errMsgId, errString);}


@Overridepublic void onAuthenticationFailed() {super.onAuthenticationFailed();}


@Overridepublic void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {//验证过程中遇到可恢复错误 super.onAuthenticationHelp(helpMsgId, helpString);}}


onAuthenticationSucceeded 和 onAuthenticationError 的回调意味着本次的认证结束,会根据当前所处业务场景给予用户不同的引导。


而 onAuthenticationFailed 和 onAuthenticationHelp 的情况,四个业务场景都是一样的,都是在界面上提示用户,我们可以合并一起处理。


所以我们根本不需要一个业务场景就对应一个 AuthenticationCallback 回调类,我们可以只用一个 AuthenticationCallback 回调类来根据当前所处的业务场景分发行为。但是我又不想在 onAuthenticationSucceeded 和 onAuthenticationError 的回调中有 Switch 逻辑。所以对于四个场景各不相同的 onAuthenticationSucceeded 和 onAuthenticationError 的回调方法,我们用状态模式来分离,这样把与特定状态相关的行为局部化,并且将不同场景下的行为分割开来。(需要给用户什么提示,什么操作,包括验证次数超限的处理,取决于当前所处的场景状态)


另外一点:需要在运行时刻根据状态来改变行为,比如说用户从一个正常态,转移到验证过程异常或者验证过程被劫持的状态。


验证过程异常情况,也即是说,受用户 root 或自定制情况,通过测试的同一个机型有可能验证过程异常。


验证过程被劫持,因为 Google API 只返回 true 或 false,我们当然不能无条件相信这个验证结果,所以需要在应用内产生一对非对称的密钥,保证验证过程不会被篡改。如果拿到验证结果解密失败,就进入了被劫持的状态了。


验证过程异常和验证被劫持的状态基本处理一致,都是属于用户无法再继续验证的场景,我们可以把这两个状态合为一。按照开发的思路,有异常,被劫持,那肯定是失败了,是吧? 但是按照产品的思路,其他 3 个业务场景按失败处理,但如果是关闭指纹的场景下(4. 设置页手动关闭指纹登陆),就算是失败了,也要让他去关闭成功,不然可能会出现用户手机中途 root 或极端情况下,无法关闭指纹,从而引起客诉。


按照分析我们可以发现,被劫持和验证过程异常的情况的处理,依赖于当时所处的场景,所以呢,我们无法把被劫持和验证过程异常当做一个独立的状态了。只能抽出作为一个公共方法。



为了不和业务逻辑耦合在一起,工具类包装了一层,主要封装了验证条件的判断,指纹类的初始化等等,最主要的是封装了加密类 CryptoObjectCreatorHelper ,我们考虑到安全因素,如果不加密的话,就意味着 App 无条件信任认证的结果,这个过程可能被攻击,数据可以被篡改,这是 App 在这种情况下必须承担的风险。但是这个加密过程和业务是无关的,我们不想让 Activity 层感知到,所以密钥和加密对象的销毁,会统一由工具类来把控。


为了安全,每次验证过程的密钥都不同,验证过程一结束,也就是回调 onAuthenticationSucceeded 和 onAuthenticationError 时,都需要销毁掉密钥,但是我们不想让业务层来操作,所以工具类也有自己的一个 AuthenticationCallback ,在 AuthenticationCallback 里做一些和业务无关的操作,再回调 Activity 的 AuthenticationCallbackListener 。


工具类的 CallBack 是 FingerprintManagerCompat.AuthenticationCallback 实现类,业务层的 AuthenticationCallbackListener 是自定义接口,因为不想把和业务无关的往上传递,比如说,验证成功的 AuthenticationResult ,验证错误的 typeId,这些业务并不关心。Activity 的 AuthenticationCallbackListener 会把请求统一转发给控制器 FingerPrintTypeController,在转发给控制器的前后,我们可以做一些通用的业务操作,比如说停止界面的扫描动画,发一些异步的请求等等,这个就是代理模式的应用了。


那控制器 FingerPrintTypeController 和四个场景的关系又是如何?我们看看类图。



可以看到,四个场景,对应四个状态类,控制器和状态类实现了同一个接口,在内部根据当前场景转发给对应的类, 那怎么根据场景转发给对应类?我们建立一个映射表,把场景和类对应起来。每次匹配的话只要 O(1) 复杂度。


private interface FingerPrintType {void onAuthenticationSucceeded();


void onAuthenticationError(String content);}


private class LoginAuthType implements FingerPrintType {@Overridepublic void onAuthenticationSucceeded() { }


@Overridepublic void onAuthenticationError(String content) { }}


private class ClearType implements FingerPrintType {@Overridepublic void onAuthenticationSucceeded() { }


@Overridepublic void onAuthenticationError(String content) { }}


private class LoginSettingType implements FingerPrintType {@Overridepublic void onAuthenticationSucceeded() { }


@Overridepublic void onAuthenticationError(String content) { }}


private class SettingType implements FingerPrintType {@Overridepublic void onAuthenticationSucceeded() { }


@Overridepublic void onAuthenticationError(String content) { }}


private class FingerPrintTypeController implements FingerPrintType {private Map<String, FingerPrintType> typeMappingMap = new HashMap<>();


public FingerPrintTypeController() {typeMappingMap.put(GESTURE_FINGER_SETTING, new SettingType());typeMappingMap.put(GESTURE_FINGER_LOGIN_SETTING, new LoginSettingType());typeMappingMap.put(GESTURE_FINGER_CLEAR, new ClearType());typeMappingMap.put(GESTURE_FINGER_LOGIN, new LoginAuthType());}


@Overridepublic void onAuthenticationSucceeded() {typeMappingMap.get(mType).onAuthenticationSucceeded();}


@Overridepublic void onAuthenticationError(String content) {typeMappingMap.get(mType).onAuthenticationError(content);}}


这个时候产品又说了,同样是异常情况,但是被劫持和异常过程异常的提示文案要不一样,ok,那我们将提示语和操作分离开来,提示和业务场景的对应关系也预先缓存在 Map 里,直接 get 获取具体提示,作为参数传入就可以了。


//普通异常情况提示 exceptionTipsMappingMap = new HashMap<>();exceptionTipsMappingMap.put(GESTURE_FINGER_SETTING, getString(R.string.fingerprint_no_support_fingerprint_gesture));exceptionTipsMappingMap.put(GESTURE_FINGER_LOGIN_SETTING, getString(R.string.fingerprint_no_support_fingerprint_gesture));exceptionTipsMappingMap.put(GESTURE_FINGER_CLEAR, null);exceptionTipsMappingMap.put(GESTURE_FINGER_LOGIN, getString(R.string.fingerprint_no_support_fingerprint_account));

兼容问题

1. 明明符合条件,isHardwareDetected() 返回 false?

在同一机型上调用 FingerprintManagerCompat 的 isHardwareDetected() 和 hasEnrolledFingerprints() 时候,返回的都是 false,但是调用 FingerprintManager 的 isHardwareDetected() 和 hasEnrolledFingerprints() 时,却是返回 true。


解决:是否符合指纹条件可以多加一层判断。

2. Letv X500 Android 6.0,API23 不按正常的套路回调

onAuthenticationError 和 onAuthenticationFailed,理论上应该是识别失败的情况,但是该机型点击取消指纹识别也会先回调一次 Error,如果遇到这种情况,只能根据具体项目环境中去进行规避适配了。

3. 魅族上遇到的坑

onAuthenticat


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


ionHelp 回调不按套路出牌,正常官网文档解释,这个方法的回调时机是在指纹认证期间发生可恢复性的错误时回调。结果在魅族上,启动指纹识别认证的时候就会回调这个方法,里面传递回来的信息提示是“等待按下手指”,也就是说,它的 onAuthenticationHelp 回调跟官网时机不一样,而且方法的作用也变了,它在正常的情况回调了 onAuthenticationHelp。


解决:不影响验证流程,无需解决

4. 小米 锁屏和切后台生命周期不一致

产品需求:用户锁屏或切到后台时(onStop)自动停止指纹验证,回到界面时(onResume)自动调起验证。


所以我在指纹回调方法中加入了标志位 isInAuth。onStop 时保存 isInAuth,onResume 时 isInAuth == true 则自动调起验证。


@Overridepublic void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {isInAuth = false;}


@Overridepublic void onAuthenticationError(int errMsgId, CharSequence errString) {isInAuth = false;}


@Overridepublic void onAuthenticationFailed() {isInAuth = true;}


@Overridepublic void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {isInAuth = true;}


然而小米 6、米 mix2 锁屏时的生命周期是 onAuthenticationError -> onStop;切到后台是 onStop -> onAuthenticationError。导致不同流程下拿到 isInAuth 标志位不一致,无法自动调起验证。


解决:界面指纹按钮可以手动调起验证,无需兼容处理。


小米 5 生命周期同上,但是无论是自动还是手动调起验证,马上就回调了 onAuthenticationError,也就是说 MI5 从后台切回来后,指纹验证流程中断。


解决:用一个栈来存储调用方法顺序,如果验证方法调起,马上就回调 onAuthenticationError 方法,则判定是属于兼容问题,按验证失败来解决。

5. 密钥解密失败
用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
如何在复杂业务场景中优雅实现Android指纹验证?,rust移动端跨平台开发