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 应用来说,这些并不是“锦上添花”,而是工程一开始就应该设计清楚的安全边界。
更多推荐





所有评论(0)