– 前言
需求说明:app经常要验证码验证用户手机号保证安全性,现在想接入指纹识别来代替验证码验证,不支持指纹识别的设备照常用验证码。
了解API
Android在23(Android M 6.0)新增了对指纹识别的硬件支持,应用可以通过调用系统Api实现指纹验证相关功能,相对于传统的手势,密码等验证方式,指纹验证安全性更高,速度也更快。
- Android 23(Android M 6.0)新增 指纹识别Api:FingerprintManager,开发者通过该Api打开指纹认证时,系统仅会打开设备的指纹模块监听,并不会有UI相关展示,需要开发者根据自身App要求弹出对应的交互流程。
- Android 28(Android P 9.0)新增 生物识别Api:BiometricManager,推荐替换掉原来的FM,囊括指纹、人脸、虹膜等生物特征识别,不过现阶段只开放了指纹相关。开发者使用该Api进行指纹认证时,系统在会打开设备的指纹模块监听的同时,还会弹出一个系统级的Dialog提示用户正在进行指纹解锁流程。
Goolge提供的API只是指纹跟设备中的指纹库进行比对,得到验证的状态(成功/失败…),无法得到唯一的指纹信息,也就无法绑定账户。 前端提供一个开关,当用户开启指纹后,将状态保存在本地,但每次指纹识别前都要判断满足要求才能正常走流程。
– 认证流程
第零步:在清单文件中申明权限
<!-- 使用生物特征识别、触摸传感器和指纹认证的许可-->
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
第一步:判断设备满足认证的前提要求(四步)
- Android6.0及以上版本
- 判断硬件支持指纹识别
- 判断已设置密码锁
- 判断至少注册了一个指纹
private boolean isSupportFingerprint(){
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
Toast.makeText(mContext, "系统版本过低,不支持指纹功能", Toast.LENGTH_SHORT).show();
return false;
} else {
KeyguardManager keyguardManager = mContext.getSystemService(KeyguardManager.class);
fingerprintManager = (FingerprintManager)getSystemService(FINGERPRINT_SERVICE);
if (fingerprintManager == null || !fingerprintManager.isHardwareDetected()) {
Toast.makeText(mContext, "您的系统不支持指纹功能", Toast.LENGTH_SHORT).show();
return false;
} else if (keyguardManager != null && !keyguardManager.isKeyguardSecure()) {
Toast.makeText(mContext, "请在设置界面开启锁屏密码,并录入指纹后再尝试", Toast.LENGTH_LONG).show();
return false;
} else if (!fingerprintManager.hasEnrolledFingerprints()) {
Toast.makeText(mContext, "您还没有录入指纹, 请在系统设置录入至少一个指纹", Toast.LENGTH_LONG).show();
return false;
}
return true;
}
第二步:两种设备调用不同的指纹管理类
判断版本在9.0以上还是以下,以上用FingerprintManager类进行指纹识别,并自定义监听时的Dialog,以下用BiometricManager类进行指纹识别,自动调用系统Dialog。
1.两种流程
private void selectMode(String num){
if(isSupportFingerprint()){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P){
overallIdentification();
}else {
FingerprintDialogFragment dialogFragment = new FingerprintDialogFragment();
dialogFragment.setArguments(bundle);
dialogFragment.showNow(getSupportFragmentManager(),"FingerprintDialogFragment");
dialogFragment.setClickCallBack(new FingerprintDialogFragment.ClickCallBack() {
@Override
public void clickCancel() {
dialogFragment.dismiss();
}
@Override
public void clickConfirm() {
dialogFragment.dismiss();
}
});
}
}
}
2. <9.0指纹识别、创建Dialog:FingerprintDialogFragment
1)xml文件
2)准备密钥:访问Android密钥库并生成用于加密数据的密钥
3)根据密钥生成密码
4)创建指纹认证回调接收事件通知 FingerprintHandler
public class FingerprintDialogFragment extends DialogFragment{
private ClickCallBack clickCallBack;
private Context mContext;
public final String KEY_NAME = "fingerprintKey";
private FingerprintHandler fph;
private FingerprintManager.CryptoObject cryptoObject;
private FingerprintManager fingerprintManager;
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
initWindow();
initView();
mContext = getContext();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
fingerprintManager = (FingerprintManager)mContext.getSystemService(FINGERPRINT_SERVICE);
fph = new FingerprintHandler(tvInfo);
initStart();
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
private void initStart() {
generateKey();
fph.doAuth(fingerprintManager, cryptoObject);
}
@RequiresApi(api = Build.VERSION_CODES.M)
private void generateKey() {
try {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
keyStore.load(null);
keyGenerator.init(new
KeyGenParameterSpec.Builder(KEY_NAME,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT).setBlockModes(KeyProperties.BLOCK_MODE_CBC).setUserAuthenticationRequired(true).setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7).build());
keyGenerator.generateKey();
Cipher cipher = generateCipher(keyStore);
cryptoObject = new FingerprintManager.CryptoObject(cipher);
} catch (NoSuchAlgorithmException| NoSuchPaddingException | InvalidKeyException | UnrecoverableKeyException | KeyStoreException exc) {
exce.printStackTrace();
}
}
private Cipher generateCipher(KeyStore keyStore) {
try {
Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7);
SecretKey key = (SecretKey) keyStore.getKey(KEY_NAME,null);
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher;
} catch (NoSuchAlgorithmException| NoSuchPaddingException | InvalidKeyException | UnrecoverableKeyException | KeyStoreException exc) {
exc.printStackTrace();
}
return null;
}
@RequiresApi(api = Build.VERSION_CODES.M)
class FingerprintHandler extends FingerprintManager.AuthenticationCallback {
private TextView tv;
public FingerprintHandler(TextView tv) {
this.tv = tv;
}
@Override
public void onAuthenticationError(int errorCode, CharSequence errString) {
super.onAuthenticationError(errorCode, errString);
if (errorCode == 1) {
tv.setTextColor(Color.parseColor("#C8374A"));
tv.setText(errString + "请录入指纹");
}else if(errorCode != 5){
tv.setTextColor(Color.parseColor("#C8374A"));
tv.setText(errString);
}
}
@Override
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
super.onAuthenticationHelp(helpCode, helpString);
LogUtils.d("Fingerprint---helpCode:-"+helpCode+"---helpString:"+helpString);
tv.setTextColor(Color.parseColor("#C8374A"));
tv.setText(helpString);
}
@Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
tv.setTextColor(Color.parseColor("#0c0c0c"));
tv.setText("验证成功");
if(type==1){
EventBus.getDefault().post(new FingerprintGoDealBean());
}else if(type==2){
EventBus.getDefault().post(new FingerprintTransferBean());
}
FingerprintDialogFragment.this.dismiss();
}
@Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
LogUtils.d("Fingerprint---验证失败-");
tv.setTextColor(Color.parseColor("#C8374A"));
tv.setText("验证失败");
}
public void doAuth(FingerprintManager manager, FingerprintManager.CryptoObject obj) {
CancellationSignal signal = new CancellationSignal();
try {
manager.authenticate(obj, signal, 0, this, null);
} catch (SecurityException sce) {
}
}
}
private void initView() {
tvCancel.setOnClickListener(v -> clickCallBack.clickCancel());
tvConfirm.setOnClickListener(v -> clickCallBack.clickConfirm());
}
interface ClickCallBack {
void clickCancel();
void clickConfirm();
}
public void setClickCallBack(ClickCallBack clickCallBack) {
this.clickCallBack = clickCallBack;
}
private void initWindow() {
setCancelable(false);
Window window = getDialog().getWindow();
window.setBackgroundDrawableResource(R.drawable.shape_body_white_10);
WindowManager.LayoutParams lp = window.getAttributes();
lp.width = 800;
lp.gravity = Gravity.CENTER;
window.setAttributes(lp);
}
}
2. >9.0指纹识别
系统提供的Dialog:(每个厂商不一样)
已知的错误状态码errorCode含义:1目前不可用,稍后再试、3超时(与设备和传感器类型有关)、4设备可用存储空间不足、5取消验证、7失败次数太多等待30s、9失败太多次生物验证锁定、10中途取消、13点击了negative button
@RequiresApi(api = Build.VERSION_CODES.P)
private void overallIdentification(){
BiometricPrompt prompt = new BiometricPrompt.Builder(mContext).setTitle("指纹验证").setSubtitle(" ").setDescription("请触摸指纹传感器")
.setNegativeButton("使用验证码", command -> {
}, (dialog, which) -> {
}).build();
BiometricPrompt.AuthenticationCallback callback = new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
ToastUtils.showMessage("验证失败");
}
@Override
public void onAuthenticationError(int errorCode, CharSequence errString) {
super.onAuthenticationError(errorCode, errString);
if(errorCode!=10){
ToastUtils.showMessage(errString+"");
}
}
@Override
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
super.onAuthenticationHelp(helpCode, helpString);
ToastUtils.showMessage(helpString+"");
}
@Override
public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
}
};
CancellationSignal signal = new CancellationSignal();
prompt.authenticate(signal,mContext.getMainExecutor(),callback);
}
参考博客: https://cloud.tencent.com/developer/article/1474404 https://www.jianshu.com/p/74fb11da9a41 https://blog.csdn.net/qq_34676644/article/details/118758483
|