HarmonyOS 7 User Authentication Kit + ArkUI:认证实例单次使用约束下的重复点击隔离、取消态收敛与页面销毁清理【鸿蒙心迹】
用户认证接到业务页面后,问题往往不是“能不能拉起系统页”,而是连续点击、立即取消、页面退出、旧实例复用。
官方 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、清空引用。对高敏感操作来说,不复用旧实例、不让旧回调穿透页面,本身就是可靠性。
更多推荐




所有评论(0)