鸿蒙NEXT开发手机的生物认证(指纹、人脸、密码)工具类(ArkTs)

发布于:2025-04-04 ⋅ 阅读:(15) ⋅ 点赞:(0)
import { RandomUtil } from './RandomUtil';
import { userAuth } from '@kit.UserAuthenticationKit';
import { BusinessError, Callback } from '@kit.BasicServicesKit';
import { LogUtil } from './LogUtil';
import { ToastUtil } from './ToastUtil';
import { StrUtil } from './StrUtil';

/**
 * 认证参数类
 */
export class AuthOptions {
  challenge?: Uint8Array; // 挑战值,用来防重放攻击。最大长度为32字节,可传Uint8Array([])。
  authType: userAuth.UserAuthType[] = [userAuth.UserAuthType.FINGERPRINT, userAuth.UserAuthType.FACE, userAuth.UserAuthType.PIN]; // 认证类型列表。
  authTrustLevel: userAuth.AuthTrustLevel = userAuth.AuthTrustLevel.ATL3; // 认证信任等级。
  title: string = '请验证身份'; // 用户认证界面的标题,最大长度为500字符。
  navigationButtonText?: string; // 导航按键的说明文本,最大长度为60字符。
  showTip: boolean = false; // 是否显示提示语,默认不显示。
}

/**
 * 查询认证能力是否支持的返回参数类
 */
export interface StatusResult {
  status: boolean;
  errorCode?: number;
  errorMsg?: string;
}

/**
 * 手机的生物认证(指纹、人脸、密码)工具类
 * 需要权限 ohos.permission.ACCESS_BIOMETRIC
 *
 * author: 鸿蒙布道师
 * since: 2025/04/02
 */
export class AuthUtil {
  private static userAuthInstance: userAuth.UserAuthInstance | undefined;

  /**
   * 查询指定类型和等级的认证能力是否支持
   * @param authType - 认证类型
   * @param authTrustLevel - 认证信任等级
   * @returns 状态结果
   */
  static getAvailableStatus(authType: userAuth.UserAuthType, authTrustLevel: userAuth.AuthTrustLevel): StatusResult {
    try {
      userAuth.getAvailableStatus(authType, authTrustLevel);
      return { status: true };
    } catch (err) {
      const error = err as BusinessError;
      LogUtil.error(`AuthUtil-getAvailableStatus-异常 ~ code: ${error.code} - message: ${error.message}`);
      return { status: false, errorCode: error.code, errorMsg: AuthUtil.getErrorMsg(error.code) };
    }
  }

  /**
   * 开始认证(使用默认配置)
   * @param showTip - 是否显示提示语
   * @param callBack - 回调函数
   */
  static onStartEasy(showTip: boolean = false, callBack: Callback<userAuth.UserAuthResult>) {
    const options = new AuthOptions();
    options.showTip = showTip;
    AuthUtil.onStart(options, callBack);
  }

  /**
   * 开始认证(用户自定义配置)
   * @param options - 认证选项
   * @param callBack - 回调函数
   */
  /**
   * 开始认证(用户自定义配置)
   * @param options - 认证选项
   * @param callBack - 回调函数
   */
  static onStart(options: AuthOptions, callBack: Callback<userAuth.UserAuthResult>) {
    try {
      AuthUtil.validateAndSetDefaults(options);

      const authParam: userAuth.AuthParam = {
        challenge: options.challenge!,
        authType: options.authType,
        authTrustLevel: options.authTrustLevel,
      };

      // 定义 InstanceOptions 类型
      interface InstanceOptions {
        title: string;
        navigationButtonText?: string; // 可选字段
      }

      // 根据条件创建 instanceOptions
      const instanceOptions: InstanceOptions = options.navigationButtonText
        ? { title: options.title, navigationButtonText: options.navigationButtonText }
        : { title: options.title };

      AuthUtil.userAuthInstance = userAuth.getUserAuthInstance(authParam, instanceOptions);

      AuthUtil.userAuthInstance.on('result', {
        onResult(result) {
          callBack?.(result);
          if (userAuth.UserAuthResultCode.SUCCESS === result.result && options.showTip) {
            const errorTip = AuthUtil.getErrorMsg(result.result, '');
            if (StrUtil.isNotEmpty(errorTip)) {
              ToastUtil.showToast(errorTip);
            }
          }
        },
      });

      AuthUtil.userAuthInstance.start(); // 开始认证
    } catch (err) {
      const error = err as BusinessError;
      LogUtil.error(`AuthUtil-onStart-异常 ~ code: ${error.code} - message: ${error.message}`);
      if (options.showTip) {
        ToastUtil.showToast(AuthUtil.getErrorMsg(error.code, '认证失败:' + error.message));
      }
    }
  }

  /**
   * 取消认证
   */
  static cancel() {
    try {
      if (AuthUtil.userAuthInstance) {
        AuthUtil.userAuthInstance.cancel();
      }
    } catch (err) {
      const error = err as BusinessError;
      LogUtil.error(`AuthUtil-cancel-异常 ~ code: ${error.code} - message: ${error.message}`);
    }
  }

  /**
   * 获取错误消息
   * @param code - 错误码
   * @param defaultMsg - 默认消息
   * @returns 错误消息
   */
  static getErrorMsg(code: number, defaultMsg: string = ''): string {
    // 使用二维数组代替索引签名对象
    const errorMessages: [number, string][] = [
      [201, '权限校验失败!'],
      [202, '系统API权限校验失败!'],
      [401, '参数检查失败!'],
      [801, '该设备不支持此API!'],
      [12500001, '认证失败!'],
      [12500002, '一般的操作错误!'],
      [12500003, '认证被取消!'],
      [12500004, '认证操作超时!'],
      [12500005, '认证类型不支持!'],
      [12500006, '认证信任等级不支持!'],
      [12500007, '认证服务已经繁忙!'],
      [12500009, '认证被锁定!'],
      [12500010, '该类型的凭据没有录入!'],
      [12500011, '认证被控件取消!'],
      [12700001, '人脸录入过程中的操作失败!'],
    ];

    // 查找匹配的错误码
    const errorMessage = errorMessages.find((item) => item[0] === code)?.[1];
    return errorMessage || defaultMsg;
  }

  /**
   * 校验并设置默认值
   * @param options - 认证选项
   */
  private static validateAndSetDefaults(options: AuthOptions) {
    if (!options.challenge) {
      options.challenge = AuthUtil.getChallenge();
    }
    if (!options.authType || options.authType.length === 0) {
      options.authType = [userAuth.UserAuthType.FINGERPRINT, userAuth.UserAuthType.FACE, userAuth.UserAuthType.PIN];
    }
    if (!options.authTrustLevel) {
      options.authTrustLevel = userAuth.AuthTrustLevel.ATL3;
    }
    if (!options.title) {
      options.title = '请验证身份';
    }
    if (options.showTip === undefined) {
      options.showTip = false;
    }
  }

  /**
   * 生成挑战值
   * @returns Uint8Array 类型的挑战值
   */
  private static getChallenge(): Uint8Array {
    // 使用传统循环生成随机数数组
    const array: number[] = [];
    for (let i = 0; i < 30; i++) {
      array.push(RandomUtil.getRandomNumber(1, 100));
    }
    return new Uint8Array(array);
  }
}

代码如下:
import { RandomUtil } from './RandomUtil';
import { userAuth } from '@kit.UserAuthenticationKit';
import { BusinessError, Callback } from '@kit.BasicServicesKit';
import { LogUtil } from './LogUtil';
import { ToastUtil } from './ToastUtil';
import { StrUtil } from './StrUtil';

/**
 * 认证参数类
 */
export class AuthOptions {
  challenge?: Uint8Array; // 挑战值,用来防重放攻击。最大长度为32字节,可传Uint8Array([])。
  authType: userAuth.UserAuthType[] = [userAuth.UserAuthType.FINGERPRINT, userAuth.UserAuthType.FACE, userAuth.UserAuthType.PIN]; // 认证类型列表。
  authTrustLevel: userAuth.AuthTrustLevel = userAuth.AuthTrustLevel.ATL3; // 认证信任等级。
  title: string = '请验证身份'; // 用户认证界面的标题,最大长度为500字符。
  navigationButtonText?: string; // 导航按键的说明文本,最大长度为60字符。
  showTip: boolean = false; // 是否显示提示语,默认不显示。
}

/**
 * 查询认证能力是否支持的返回参数类
 */
export interface StatusResult {
  status: boolean;
  errorCode?: number;
  errorMsg?: string;
}

/**
 * 手机的生物认证(指纹、人脸、密码)工具类
 * 需要权限 ohos.permission.ACCESS_BIOMETRIC
 *
 * author: 鸿蒙布道师
 * since: 2025/04/02
 */
export class AuthUtil {
  private static userAuthInstance: userAuth.UserAuthInstance | undefined;

  /**
   * 查询指定类型和等级的认证能力是否支持
   * @param authType - 认证类型
   * @param authTrustLevel - 认证信任等级
   * @returns 状态结果
   */
  static getAvailableStatus(authType: userAuth.UserAuthType, authTrustLevel: userAuth.AuthTrustLevel): StatusResult {
    try {
      userAuth.getAvailableStatus(authType, authTrustLevel);
      return { status: true };
    } catch (err) {
      const error = err as BusinessError;
      LogUtil.error(`AuthUtil-getAvailableStatus-异常 ~ code: ${error.code} - message: ${error.message}`);
      return { status: false, errorCode: error.code, errorMsg: AuthUtil.getErrorMsg(error.code) };
    }
  }

  /**
   * 开始认证(使用默认配置)
   * @param showTip - 是否显示提示语
   * @param callBack - 回调函数
   */
  static onStartEasy(showTip: boolean = false, callBack: Callback<userAuth.UserAuthResult>) {
    const options = new AuthOptions();
    options.showTip = showTip;
    AuthUtil.onStart(options, callBack);
  }

  /**
   * 开始认证(用户自定义配置)
   * @param options - 认证选项
   * @param callBack - 回调函数
   */
  /**
   * 开始认证(用户自定义配置)
   * @param options - 认证选项
   * @param callBack - 回调函数
   */
  static onStart(options: AuthOptions, callBack: Callback<userAuth.UserAuthResult>) {
    try {
      AuthUtil.validateAndSetDefaults(options);

      const authParam: userAuth.AuthParam = {
        challenge: options.challenge!,
        authType: options.authType,
        authTrustLevel: options.authTrustLevel,
      };

      // 定义 InstanceOptions 类型
      interface InstanceOptions {
        title: string;
        navigationButtonText?: string; // 可选字段
      }

      // 根据条件创建 instanceOptions
      const instanceOptions: InstanceOptions = options.navigationButtonText
        ? { title: options.title, navigationButtonText: options.navigationButtonText }
        : { title: options.title };

      AuthUtil.userAuthInstance = userAuth.getUserAuthInstance(authParam, instanceOptions);

      AuthUtil.userAuthInstance.on('result', {
        onResult(result) {
          callBack?.(result);
          if (userAuth.UserAuthResultCode.SUCCESS === result.result && options.showTip) {
            const errorTip = AuthUtil.getErrorMsg(result.result, '');
            if (StrUtil.isNotEmpty(errorTip)) {
              ToastUtil.showToast(errorTip);
            }
          }
        },
      });

      AuthUtil.userAuthInstance.start(); // 开始认证
    } catch (err) {
      const error = err as BusinessError;
      LogUtil.error(`AuthUtil-onStart-异常 ~ code: ${error.code} - message: ${error.message}`);
      if (options.showTip) {
        ToastUtil.showToast(AuthUtil.getErrorMsg(error.code, '认证失败:' + error.message));
      }
    }
  }

  /**
   * 取消认证
   */
  static cancel() {
    try {
      if (AuthUtil.userAuthInstance) {
        AuthUtil.userAuthInstance.cancel();
      }
    } catch (err) {
      const error = err as BusinessError;
      LogUtil.error(`AuthUtil-cancel-异常 ~ code: ${error.code} - message: ${error.message}`);
    }
  }

  /**
   * 获取错误消息
   * @param code - 错误码
   * @param defaultMsg - 默认消息
   * @returns 错误消息
   */
  static getErrorMsg(code: number, defaultMsg: string = ''): string {
    // 使用二维数组代替索引签名对象
    const errorMessages: [number, string][] = [
      [201, '权限校验失败!'],
      [202, '系统API权限校验失败!'],
      [401, '参数检查失败!'],
      [801, '该设备不支持此API!'],
      [12500001, '认证失败!'],
      [12500002, '一般的操作错误!'],
      [12500003, '认证被取消!'],
      [12500004, '认证操作超时!'],
      [12500005, '认证类型不支持!'],
      [12500006, '认证信任等级不支持!'],
      [12500007, '认证服务已经繁忙!'],
      [12500009, '认证被锁定!'],
      [12500010, '该类型的凭据没有录入!'],
      [12500011, '认证被控件取消!'],
      [12700001, '人脸录入过程中的操作失败!'],
    ];

    // 查找匹配的错误码
    const errorMessage = errorMessages.find((item) => item[0] === code)?.[1];
    return errorMessage || defaultMsg;
  }

  /**
   * 校验并设置默认值
   * @param options - 认证选项
   */
  private static validateAndSetDefaults(options: AuthOptions) {
    if (!options.challenge) {
      options.challenge = AuthUtil.getChallenge();
    }
    if (!options.authType || options.authType.length === 0) {
      options.authType = [userAuth.UserAuthType.FINGERPRINT, userAuth.UserAuthType.FACE, userAuth.UserAuthType.PIN];
    }
    if (!options.authTrustLevel) {
      options.authTrustLevel = userAuth.AuthTrustLevel.ATL3;
    }
    if (!options.title) {
      options.title = '请验证身份';
    }
    if (options.showTip === undefined) {
      options.showTip = false;
    }
  }

  /**
   * 生成挑战值
   * @returns Uint8Array 类型的挑战值
   */
  private static getChallenge(): Uint8Array {
    // 使用传统循环生成随机数数组
    const array: number[] = [];
    for (let i = 0; i < 30; i++) {
      array.push(RandomUtil.getRandomNumber(1, 100));
    }
    return new Uint8Array(array);
  }
}