用户认证接到业务页面后,问题往往不是“能不能拉起系统页”,而是连续点击、立即取消、页面退出、旧实例复用。

官方 API 对 UserAuthInstance 的约束很明确:一个实例只用于一次认证。取消后继续复用旧实例,状态容易失控。

这次用 AuthFlowGuardLab 把单次实例、重复点击、取消和页面销毁放进同一条状态机。Session 为 auth_guard_20261001_15,指纹认证,ATL2,点击 3 次,启动 2 次,忽略 1 次重复点击,中间取消 1 次,最后认证成功。

一、先检查能力,再创建“本次认证专用实例”

每次准备认证前,我会先检查设备是否支持目标认证类型和可信等级。

private checkAvailable(): boolean {
  try {
    userAuth.getAvailableStatus(
      userAuth.UserAuthType.FINGERPRINT,
      userAuth.AuthTrustLevel.ATL2
    )
    return true
  } catch (_) {
    return false
  }
}

能力检查不拉起认证页;真正开始时,每次都创建新的 UserAuthInstance:

private createInstance():
  userAuth.UserAuthInstance {
  const authParam: userAuth.AuthParam = {
    challenge:
      new Uint8Array([1, 5, 1, 8, 0, 7]),
    authType: [
      userAuth.UserAuthType.FINGERPRINT
    ],
    authTrustLevel:
      userAuth.AuthTrustLevel.ATL2
  }

  const widgetParam:
    userAuth.WidgetParam = {
    title: '验证身份'
  }

  return userAuth.getUserAuthInstance(
    authParam,
    widgetParam
  )
}

正式项目里的 challenge 应来自当前业务会话,用来把认证和当前操作绑定。

二、重复点击不是再 start 一次,而是直接隔离

当前已经是 AUTHENTICATING 时,不创建第二个实例,也不对同一实例再次 start。

private async startAuth(): Promise<void> {
  this.attempts++

  if (this.state === 'AUTHENTICATING') {
    this.ignoredTaps++
    return
  }

  if (!this.checkAvailable()) {
    this.state = 'UNAVAILABLE'
    return
  }

  this.releaseCurrentInstance()

  const generation = ++this.generation
  const instance = this.createInstance()

  this.authInstance = instance
  this.started++
  this.state = 'AUTHENTICATING'

  this.resultCallback = {
    onResult: (
      result: userAuth.UserAuthResult
    ) => {
      this.handleResult(
        generation,
        result
      )
    }
  }

  instance.on(
    'result',
    this.resultCallback
  )

  instance.start()
}

最终 Attempts=3、Started=2、Ignored Taps=1。generation 标记回调属于哪一轮;一旦取消或重新发起,旧 generation 就不能再改页面。

三、取消不是失败,状态要单独收敛

取消有独立业务语义,我不会把它统一映射成 FAILED。

private cancelAuth(): void {
  if (!this.authInstance) {
    return
  }

  this.cancelCount++
  this.generation++

  try {
    this.authInstance.cancel()
  } finally {
    this.state = 'CANCELED'
    this.releaseCurrentInstance()
  }
}

取消时先让 generation 失效,再调用 cancel,晚到的旧结果会被丢弃。本次先取消一次,再创建新实例成功认证,因此 Cancel Count=1、Generation=3。

四、结果回调必须先判断是不是“当前这一轮”

private handleResult(
  generation: number,
  result: userAuth.UserAuthResult
): void {
  if (generation !== this.generation) {
    return
  }

  if (result.result ===
      userAuth.UserAuthResultCode.SUCCESS) {
    this.result = 'SUCCESS'
    this.state = 'AUTHENTICATED'
  } else if (result.result === 12500003) {
    this.result = 'CANCELED'
    this.state = 'CANCELED'
  } else {
    this.result = `CODE_${result.result}`
    this.state = 'FAILED'
  }

  this.lastResult = '15:18:07'
  this.releaseCurrentInstance()
}

取消码单独处理;超时、锁定、未录入凭据等结果也应继续细分。正式项目应使用 SDK 常量,不把裸错误码散落在页面里。

五、释放动作必须是 on/off 成对出现

页面退出或者认证结束后,我都统一调用:

private releaseCurrentInstance(): void {
  if (!this.authInstance) {
    return
  }

  if (this.resultCallback) {
    this.authInstance.off(
      'result',
      this.resultCallback
    )
  }

  this.authInstance = undefined
  this.resultCallback = undefined
}

aboutToDisappear(): void {
  if (this.state === 'AUTHENTICATING') {
    try {
      this.authInstance?.cancel()
    } catch (_) {}
  }

  this.generation++
  this.releaseCurrentInstance()
}

这一步避免页面退出后旧 listener 继续回写 UI。UserAuthInstance 不做应用级单例,而是跟一次认证会话一起创建、一起结束。

六、项目里页面只展示按钮和诊断信息,AuthFlowCoordinator 负责 capability check、实例创建、on/off、cancel、generation 和结果映射。HiLog 重点保留能力检查、重复点击、取消、新 generation、SUCCESS 和 listener 清理。

七、最终运行页要证明“成功前确实经历过一次取消”

最终页面数据显示:

  • Session:auth_guard_20261001_15
  • Auth Type:FINGERPRINT
  • Trust Level:ATL2
  • State:AUTHENTICATED
  • Attempts:3
  • Started:2
  • Ignored Taps:1
  • Cancel Count:1
  • Result:SUCCESS
  • Generation:3
  • Active Instance:false
  • Last Result:15:18:07

Active Instance=false 表示成功后没有继续持有旧实例,下一次认证会重新创建。

八、正式产品里还要考虑锁定、超时和凭据变化

正式产品还要处理凭据删除、锁定、超时和服务繁忙;服务端会话与 Token 有效期则属于另一层业务。

九、这次真正固定下来的是“一次实例,一次认证”

状态链路:

IDLE → AUTHENTICATING → CANCELED → NEW_INSTANCE → AUTHENTICATED → CLEANUP

重复点击不创建并行认证,取消让旧 generation 失效,重新认证创建新实例,页面退出则 cancel、off、清空引用。对高敏感操作来说,不复用旧实例、不让旧回调穿透页面,本身就是可靠性。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐