news 2026/9/30 7:24:38

HarmonyOS 7 + User Authentication Kit + Asset Store Kit 技术干货:生物认证、敏感操作授权与关键资产安全闭环【鸿蒙心迹】

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HarmonyOS 7 + User Authentication Kit + Asset Store Kit 技术干货:生物认证、敏感操作授权与关键资产安全闭环【鸿蒙心迹】

认证成功并不等于“敏感数据就安全了”。真正完整的安全链路,应该把“谁在操作、这次操作有没有被授权、授权之后能访问什么、敏感数据存在哪里”几件事连起来。这篇文章用一个安全资产中心 Demo,把 User Authentication Kit 与 Asset Store Kit 的职责边界和工程落地过程拆开来看。

一、为什么我不再把“指纹验证”当成一个登录按钮

第一次在应用里接用户认证时,最容易做成这样的流程:点按钮,弹出指纹或人脸认证,成功以后进入下一页。功能看起来完整,实际只是把“认证”做出来了。

问题在于,真实业务里的敏感操作远不止登录。

比如查看 API 密钥、读取登录凭据、进入隐私相册、导出敏感资料、修改支付设置,这些动作往往发生在用户已经登录以后。此时真正要解决的不是“用户是不是登录过”,而是当前这一次敏感操作,是否经过了可信身份确认。

这也是 User Authentication Kit 更适合承担的角色。系统提供统一身份认证接口,可结合锁屏口令、人脸、指纹等认证方式,对敏感操作做二次确认。认证凭据本身由系统安全环境处理,应用拿到的是认证结果,而不是生物特征原始数据。

但认证只是第一层。

如果认证成功后,应用仍然把 API Key、Token 或密码直接明文写到 Preferences、数据库字段甚至普通文件里,那么前面的认证更多只是“门口有一道锁”,资产本身并没有进入更合适的存储区域。

所以我这次把需求拆成两段:

User Authentication Kit 负责证明“当前操作者是谁、这次敏感动作是否获授权”;Asset Store Kit 负责保存和管理短敏感数据。

把两层分开之后,整个安全模型才真正立住。

二、先定边界:认证不是存储,存储也不是认证

这类需求最容易踩的第一个坑,就是把两个能力混成一个概念。

User Authentication Kit 的重点是“认证动作”:

  • 当前设备是否存在可用认证凭据;
  • 业务需要什么认证类型;
  • 认证信任等级如何设置;
  • 用户取消、超时、冻结时怎么处理;
  • 认证结果是否允许继续敏感操作。

Asset Store Kit 的重点则完全不同:

  • 哪些数据属于关键资产;
  • 如何新增、查询、更新、删除关键资产;
  • 是否要求认证保护;
  • 数据读取的条件如何限制;
  • 敏感数据怎样避免暴露到普通业务存储。

换句话说,一个解决“人”,一个解决“数据”。

这次 Demo 我把它设计成一个“安全资产中心”。页面里有三类敏感内容:API 密钥、登录凭据和加密业务数据。没有完成认证时,只显示资产名称和掩码;通过认证以后,才允许进入资产详情。

这个页面的价值不在视觉,而在于它把安全边界直接反映到了交互上。用户能看到资产存在,但不能因为“已经进了应用”就默认拥有读取权限。

三、发起认证之前,先别急着 start()

在工程里,我更建议把认证过程单独封装,而不是在页面按钮里直接写一堆认证代码。

原因很简单:认证不是普通异步方法,它有明确的状态和结果分支。用户可能认证成功,也可能取消、超时、凭据未录入,甚至当前认证方式处于锁定状态。如果这些判断全堆在页面里,后面再增加其他敏感入口时,逻辑很快会重复。

下面这段代码就是我会保留的一层认证服务。示例使用较新的getUserAuthInstance()方式,不再走旧的getAuthInstance():

import { userAuth } from '@kit.UserAuthenticationKit' import { cryptoFramework } from '@kit.CryptoArchitectureKit' export class AuthService { authenticate(): Promise<boolean> { return new Promise((resolve, reject) => { const random = cryptoFramework.createRandom() const challenge = random.generateRandomSync(16).data const authParam: userAuth.AuthParam = { challenge, authType: [ userAuth.UserAuthType.FINGERPRINT, userAuth.UserAuthType.FACE, userAuth.UserAuthType.PIN ], authTrustLevel: userAuth.AuthTrustLevel.ATL3 } const widgetParam: userAuth.WidgetParam = { title: '验证身份后访问安全资产' } const instance = userAuth.getUserAuthInstance(authParam, widgetParam) instance.on('result', { onResult: (result) => { if (result.result === userAuth.UserAuthResultCode.SUCCESS) { resolve(true) } else { resolve(false) } } }) try { instance.start() } catch (err) { reject(err) } }) } }

这段代码里我最在意的不是参数本身,而是三件事。

第一,challenge每次认证都应该是业务这一次认证上下文的一部分,而不是长期复用一个固定值。第二,认证方式最好根据业务风险等级做组合,而不是所有场景都只用一种方式。第三,认证实例本身是“一次认证一次实例”的思路,完成后不要把它当成长生命周期对象继续复用。

另外,涉及生物认证时要关注对应权限和设备能力。如果设备没有录入相应凭据,页面不应该只报“认证失败”,而应该把“未录入”“当前不支持”“认证被冻结”等状态转换成用户能理解的提示。

四、认证结果不要直接变成“全局通行证”

做完认证以后,还有一个很容易被忽略的问题:认证成功到底授权什么?

如果实现成“认证一次,整个应用之后都可以访问所有敏感数据”,安全边界就被放得太大了。

我更推荐把认证结果和敏感操作绑定。

比如用户点“查看 API 密钥”,认证成功后只允许完成这次 API 密钥读取;用户稍后再点“导出全部凭据”,应该重新判断是否需要认证,而不是因为刚才认证过一次就永久放行。

业务上可以维护一个非常轻的授权上下文:

interface SensitiveActionGrant { action: 'READ_API_KEY' | 'READ_CREDENTIAL' | 'EXPORT_SECRET' grantedAt: number expireAt: number } class GrantManager { private currentGrant?: SensitiveActionGrant grant(action: SensitiveActionGrant['action'], ttlMs: number = 60_000) { const now = Date.now() this.currentGrant = { action, grantedAt: now, expireAt: now + ttlMs } } canAccess(action: SensitiveActionGrant['action']): boolean { if (!this.currentGrant) return false return this.currentGrant.action === action && Date.now() < this.currentGrant.expireAt } clear() { this.currentGrant = undefined } }

这不是为了自己再造一套安全认证,而是为了把“系统认证结果”和“业务操作范围”收口。系统负责证明用户身份,业务负责决定认证成功后允许做什么。

这两层不要混。

五、敏感数据落地时,Asset Store 才是第二道核心边界

认证完成以后,我才会进入资产读取或写入逻辑。

Asset Store Kit 的定位非常适合这类短敏感数据,比如密码类数据、Token、账号凭据、密钥材料等。它和普通数据库最大的区别,不是“API 写法不一样”,而是它从设计上就面向关键资产的安全存储和管理。

所以我的业务代码里不会出现“认证成功以后从普通 JSON 文件把密码读出来”这种路径,而是通过独立的 AssetService 去处理关键资产。

示意结构可以这样写:

import { asset } from '@kit.AssetStoreKit' export class AssetService { async saveCredential(alias: string, secret: Uint8Array): Promise<void> { const attrs: asset.AssetMap = new Map() attrs.set(asset.Tag.ALIAS, alias) attrs.set(asset.Tag.SECRET, secret) attrs.set(asset.Tag.ACCESSIBILITY, asset.Accessibility.DEVICE_FIRST_UNLOCKED) await asset.add(attrs) } async queryCredential(alias: string): Promise<asset.AssetResult[]> { const query: asset.AssetMap = new Map() query.set(asset.Tag.ALIAS, alias) query.set(asset.Tag.RETURN_TYPE, asset.ReturnType.ALL) return await asset.query(query) } }

不同 SDK 版本里的枚举、属性名和可配置项可能继续变化,接入时应该以当前 API 参考为准,但架构思路不会变:敏感数据不要绕过资产存储层直接进入普通业务存储。

六、真正有价值的是“认证 → 授权 → 资产访问”三段式链路

做到这里以后,页面逻辑就可以变得很清楚。

用户点击某个敏感资产时,页面并不直接查询 Asset Store,而是先检查当前操作是否已有有效授权;如果没有,就发起系统身份认证;认证成功后创建本次业务授权,再访问 Asset Store;操作完成后根据策略清理授权上下文。

这种流程看起来比“点一下直接读数据”多了几步,但换来的好处很明显:

  • 用户认证和数据读取解耦;
  • 每个敏感操作都能配置不同的风险等级;
  • 资产存储逻辑集中在单独服务层;
  • 日志可以明确记录“认证成功”和“资产读取成功”是两个不同事件;
  • 后续增加自动锁定、授权超时、敏感操作审计都比较容易。

开发阶段我会把这些节点全部打日志,而不是只看页面有没有跳转。

像这类安全能力,页面“看起来成功”不代表链路真的正确。最少要确认:认证实例有没有成功启动、结果回调有没有收到、资产查询有没有被认证结果约束、异常退出后授权上下文有没有被清理。

七、不要把认证结果缓存成一个永久 boolean

安全类功能里,我最不推荐的一种写法就是:

@State isAuthed: boolean = true

然后整个应用只要isAuthed === true,所有敏感数据都可以访问。

这种状态非常危险,因为它没有时间、动作、页面生命周期和应用前后台切换边界。

更稳妥的做法是:

  • 授权有有效期;
  • 授权绑定具体敏感操作;
  • 应用进入后台或关键页面销毁时清理;
  • 资产读取时再次检查授权,而不是只依赖 UI 是否显示;
  • 对高风险操作保持“一次认证一次授权”。

如果业务确实需要复用设备解锁结果,也应该使用系统提供的认证结果复用能力,并明确配置复用模式和有效时间,而不是自己长期缓存一个“认证成功”标记。

八、资产详情页应该把“为什么现在能访问”展示出来

安全功能最怕做成一个完全不可解释的黑盒。

所以这次 Demo 我专门做了一页“资产访问详情”。认证成功以后,页面会展示:

  • 本次使用的认证方式;
  • 授权时间;
  • 授权有效范围;
  • Asset Store 当前状态;
  • 最近敏感操作记录。

这种信息对普通用户可以适当精简,但对开发调试特别有价值。它能快速确认当前页面为什么能访问、哪一层授权正在生效、资产读取发生在什么时间。

尤其是安全问题出现时,比“我记得刚才认证过”可靠得多。

九、几个真正值得提前处理的边界

这类工程做到最后,真正影响稳定性的往往不是主流程,而是边界。

认证取消时,不要把它当成系统异常;认证超时时,页面应该恢复到可再次发起认证的状态;凭据未录入时,要提示用户先去系统录入,而不是无限重试;认证服务忙或暂时冻结时,也不能继续读取资产。

资产层同样如此。新增资产要考虑别名冲突,更新要确认目标是否存在,删除后 UI 缓存要清掉,读取结果不要长期驻留内存,也尽量不要把敏感明文写进 HiLog。

很多安全问题不是“算法不够强”,而是敏感信息被调试日志、页面状态、缓存对象或异常堆栈无意间泄露出来。

所以安全链路真正需要保护的,不只是落盘后的数据,还包括数据在业务代码中短暂经过的每一个节点。

十、本文小记

这次把 User Authentication Kit 和 Asset Store Kit 放到一条真实业务链里以后,我对这两个能力的认识比单独看 API 清楚很多。

User Authentication Kit 解决的不是“做一个指纹登录页”,而是敏感操作前的身份确认。Asset Store Kit 解决的也不是“换一种 key-value 存储”,而是把短敏感数据放进更符合其安全属性的存储体系。

两者真正组合起来以后,才形成一条完整链路:

用户发起敏感操作 → 系统身份认证 → 业务限定授权范围 → Asset Store 读取关键资产 → 操作完成后收回授权。

如果后面继续扩展,我会进一步加入认证结果复用策略、前后台切换自动锁定、资产访问审计和不同风险等级的认证策略。对于涉及账号凭据、Token、隐私数据的 HarmonyOS 应用来说,这些并不是“锦上添花”,而是工程一开始就应该设计清楚的安全边界。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 7:23:50

初识 Redis:从「是什么」到「怎么用」

一.背景引入 刚接触 Redis 的人&#xff0c;最常问的一句话是&#xff1a;redis到底能解决什么问题&#xff0c;为什么大家都用它。要理解这一点,我们应该先从它的历史聊起,2008 年&#xff0c;作者 Salvatore Sanfilippo 在开发一个叫 LLOOGG 的网站时&#xff0c;需要一个高…

作者头像 李华
网站建设 2026/9/30 7:22:45

财务人记住:别替领导扛责任,善良要有锋芒

财务人最容易吃亏的地方&#xff0c;往往不是不会做事&#xff0c;而是太愿意把事情做完。业务数据没交&#xff0c;自己补&#xff1b;领导没有明确表态&#xff0c;先按经验处理&#xff1b;项目出了问题&#xff0c;也习惯第一时间帮忙兜住。事情顺利时&#xff0c;大家觉得…

作者头像 李华
网站建设 2026/9/30 7:22:24

华为没有公开 MetaERP 预算主数据批量导入的源码/表级机制,但按它元数据驱动 + 微服务 + 云原生批量管道的架构,以及 Fusion / EBS / Sage Intacct 等高端 ERP

华为没有公开 MetaERP 预算主数据批量导入的源码/表级机制&#xff0c;但按它元数据驱动 微服务 云原生批量管道的架构&#xff0c;以及 Fusion / EBS / Sage Intacct 等高端 ERP 的通用做法&#xff0c;可以很确定地说&#xff1a;预算主数据批量导入 模板/文件/消息 → 接…

作者头像 李华
网站建设 2026/9/30 7:21:55

类和对象(四)

在 C 的面向对象编程中&#xff0c;构造函数是每个类都绕不开的核心话题。它负责在对象创建时完成初始化&#xff0c;是对象生命周期中第一个被调用的成员函数。本文将继续深入探讨构造函数的进阶用法——初始化列表&#xff0c;并进一步讲解类型转换与 static 成员这两个与对象…

作者头像 李华
网站建设 2026/9/30 7:21:37

二分查找系列一

前言 二分查找属于最恶心&#xff0c;细节最多&#xff0c;最容易写出死循环的算法。但是同是也是很简单的算法&#xff0c;因为有模板而且很容易学会。主要应用与数组有序或者无序(有规律)的情况下。 模板主要是朴素二分模板、查找左边界的二分模板、查找右边界的二分模板。…

作者头像 李华
网站建设 2026/9/30 7:21:07

开发者的偷懒小工具:CodeFencer,让代码块粘贴更舒服一点

作为一名开发者&#xff0c;每次写技术博客最烦的事情是什么&#xff1f;对我来说&#xff0c;不是排版&#xff0c;不是配图&#xff0c;而是粘贴代码。从编辑器复制一段代码&#xff0c;贴到 CSDN 里&#xff0c;格式乱了&#xff0c;还得手动选中、点代码块、选语言。或者更…

作者头像 李华