【HarmonyOS 7新能力|036】分布式数字身份工程封装:把接入逻辑放进可维护的分层结构

【HarmonyOS 7新能力|036】分布式数字身份工程封装:把接入逻辑放进可维护的分层结构

数字身份场景常见的错误,是为了证明一个简单事实而传递整份身份资料:只需要确认"已成年",却上传姓名、证件号码和完整出生日期。分布式数字身份真正值得关注的工程价值,是让用户持有凭证,并在明确用途下只披露完成业务所需的声明。要实现这一点,项目不能把凭证读取、授权弹窗、证明生成和业务放行揉在一个页面中。

说明:本文使用的 IdentityOrchestratorCredentialPort 等均为教学抽象,不对应某个 HarmonyOS SDK 的真实类名或接口签名。DID、可验证凭证、密码学套件、设备支持及合规要求,请以当前官方文档、目标 SDK 与实际业务所在地规则为准。

1. 身份验证的输入应是声明,而不是证件

业务首先描述自己需要验证的事实,例如年龄满足范围、某资质有效、会员状态成立。请求中要包含用途、接收方、有效时间和是否允许替代声明,而不是预设必须取出哪张证件的哪些字段。

ts 复制代码
export interface ClaimRequest {
  requestId: string
  verifierId: string
  purpose: string
  claims: readonly ClaimRequirement[]
  expiresAt: number
  nonce: string
}

export interface ClaimRequirement {
  type: string
  predicate?: string
  required: boolean
}

这种建模把业务目标与凭证格式分开。未来更换凭证发行方或证明协议时,业务页面仍然只关心声明是否满足。

2. 四层架构约束数据流向

业务页面展示用途并接收验证结果;身份编排层校验请求、组织授权和选择凭证;能力适配层隔离钱包、证明与验证接口;凭证仓库管理索引、状态和受保护存储。上层只依赖抽象能力,凭证原文不应穿过所有层级。

ts 复制代码
export interface CredentialPort {
  findCandidates(requirements: readonly ClaimRequirement[]): Promise<CredentialSummary[]>
  createPresentation(input: PresentationInput): Promise<PresentationResult>
}

export interface VerificationPort {
  verify(presentation: string, request: ClaimRequest): Promise<VerifyResult>
}

端口应保持最小,避免把底层 SDK 对象直接返回页面。平台升级只修改适配器,授权规则和业务结果映射则留在编排层。

3. 请求进入后先做真实性和时效校验

应用不能因为请求长得像 JSON 就信任它。编排器应检查接收方标识、用途是否为空、时间窗口、随机数、声明白名单和请求完整性。来自二维码、网页或跨设备的输入都视为不可信边界。

ts 复制代码
export function validateRequest(req: ClaimRequest, now: number): ValidationResult {
  if (!req.requestId || !req.verifierId || !req.purpose) return invalid('MISSING_CONTEXT')
  if (req.expiresAt <= now) return invalid('REQUEST_EXPIRED')
  if (!req.nonce || req.claims.length === 0) return invalid('INVALID_CHALLENGE')
  if (req.claims.some(item => !claimAllowList.has(item.type))) return invalid('CLAIM_NOT_ALLOWED')
  return valid()
}

随机数与请求 ID 用于防止旧证明被简单复用。具体签名校验与信任链必须调用经过验证的平台或密码学实现,不能自创算法。

4. 用户授权必须具体、可理解

"是否同意授权"信息量不足。授权页需要清楚展示接收方、用途、这一次要证明的声明、是否会传出原始属性、有效期限和拒绝后的影响。可选声明默认不勾选,用户选择只对本次请求生效。

ts 复制代码
export interface ConsentViewModel {
  verifierName: string
  purpose: string
  disclosures: readonly DisclosureItem[]
  expiresText: string
  canDeny: boolean
}

授权记录与凭证内容分离,只记请求摘要、选择项和时间。不能把一次授权扩展为长期通用授权,也不能用拒绝后无限弹窗迫使用户接受。

5. 候选凭证选择遵守最小披露

同一声明可能由多张凭证满足。选择器应优先考虑披露属性更少、有效期合适、来源受信且状态可确认的候选,而不是简单拿第一张。算法输出选择理由,便于授权页解释和测试。

ts 复制代码
export function rankCandidate(c: CredentialSummary, req: ClaimRequest): number {
  const extraPenalty = c.availableClaims.filter(x => !requested(req, x)).length * 10
  const expiryPenalty = c.expiresSoon ? 20 : 0
  const trustBonus = c.issuerTrusted ? 30 : 0
  return trustBonus - extraPenalty - expiryPenalty
}

示例权重仅说明方法,不是通用标准。若协议支持谓词证明,应优先证明结论;若只能披露属性,则明确告诉用户将发送什么。

6. 凭证摘要与凭证材料分开

列表页只需要名称、发行方、可用状态和到期提示,不需要解密完整凭证。仓库返回摘要,只有用户确认并进入证明生成时,受控适配器才在最短生命周期内访问必要材料。

ts 复制代码
export interface CredentialSummary {
  id: string
  displayName: string
  issuerName: string
  availableClaims: readonly string[]
  expiresAt: number
  issuerTrusted: boolean
  expiresSoon: boolean
}

UI 不缓存原始凭证,不把材料写入日志、剪贴板或普通首选项。离开流程后清理临时对象,系统截图和后台预览也要按产品安全策略处理。

7. 证明生成必须绑定本次用途

生成的证明应绑定请求随机数、验证方、用途和时效,避免在另一个场景被重放。编排器构造严格输入,适配器完成真实密码学操作,业务层不得拼接签名或自行管理私钥。

ts 复制代码
export interface PresentationInput {
  credentialId: string
  requestedClaims: readonly string[]
  verifierId: string
  purpose: string
  nonce: string
  expiresAt: number
}

export type PresentationResult =
  | { status: 'created'; payload: string }
  | { status: 'cancelled' }
  | { status: 'failed'; code: string }

失败只向页面暴露可处理的归一化错误,底层细节进入脱敏诊断。用户取消不是异常,也不应自动重试。

8. 验证结果要与业务决定解耦

密码学验证通过只说明证明在当前规则下成立,不代表业务一定要放行。业务还可能检查请求是否过期、声明是否完整、发行方是否符合场景策略以及凭证是否撤销。验证层输出事实,策略层做决定。

ts 复制代码
export interface VerifyResult {
  cryptographicallyValid: boolean
  claimsSatisfied: readonly string[]
  missingClaims: readonly string[]
  issuerStatus: 'trusted' | 'unknown' | 'blocked'
  credentialStatus: 'active' | 'revoked' | 'unknown'
}

unknown 不能被偷偷转为通过。低风险场景可提示补充,高风险场景可拒绝或转人工流程,规则必须明确记录。

9. 状态机避免重复授权与重放

身份流程至少包含 validatingselectingconsentingpresentingverifyingcompletedcancelledfailed。状态转换由编排器统一执行,页面按钮只发送事件。

ts 复制代码
export type IdentityFlowState =
  | { kind: 'validating' }
  | { kind: 'consenting'; model: ConsentViewModel }
  | { kind: 'presenting'; requestId: string }
  | { kind: 'completed'; result: BusinessIdentityResult }
  | { kind: 'failed'; code: string; retryable: boolean }

同一个 requestId 在进行中时拒绝再次启动,完成后保存短期消费标记。重复点击、页面旋转或返回前台不能触发第二次证明生成。

10. 撤销、过期和离线边界

凭证有生命周期。仓库在使用前检查本地到期状态,适配器按官方机制确认撤销状态。若撤销信息必须联网而当前离线,结果应为 unknown,由场景策略决定是否允许稍后验证。

ts 复制代码
export interface CredentialStatusPolicy {
  requireFreshStatus: boolean
  maxStatusAgeMs: number
  allowOfflineUnknown: boolean
}

缓存撤销状态要带获取时间和来源,不能永久复用。删除凭证、清除账号或撤销授权时,应同步清理索引、临时证明和相关缓存,但保留何种合规审计需单独定义。

11. 测试聚焦隐私不变量

除了正常流程,还要测试过期请求、未知声明、用户拒绝、无匹配凭证、证明失败、验证方不可信、凭证撤销、离线未知和重复请求。更重要的是验证"不该发生的事":可选属性没有默认披露、日志没有凭证原文、页面拿不到私钥、拒绝后不会继续生成证明。

ts 复制代码
it('discloses only approved claims', async () => {
  const input = buildPresentation(['ageOver18'])
  const result = await adapter.createPresentation(input)
  expect(result).not.toContain('fullBirthDate')
  expect(result).not.toContain('documentNumber')
})

真机验收还需覆盖系统返回、后台恢复、锁屏、设备时间变化和权限拒绝。协议互操作性应与真实验证端联调,单元测试不能替代跨实现验证。

12. 落地清单与总结

落地前确认:业务请求的是声明而非整份证件;用途、接收方和时效可见;请求经过真实性校验;可选信息默认不披露;凭证材料只在受控层短暂访问;证明绑定本次随机数和验证方;验证事实与业务决策分离;撤销、过期、离线都有明确状态;日志与缓存不含敏感原文;用户可以拒绝和撤销。

分布式数字身份的工程目标不是把传统证件电子化,而是重新收紧身份数据的流动路径。通过声明驱动、明确授权、候选最小化、用途绑定和分层适配,项目才能在能力演进时保持边界稳定,让"只证明必要事实"成为代码能够持续验证的不变量。


本文为 HarmonyOS 7 新能力工程实践系列第 036 篇。示例用于说明架构方法,生产接入请依据官方文档、目标 SDK、协议规范和隐私合规评审执行。

相关推荐
HwJack201 小时前
【HarmonyOS开发小实践】ArkUI 交互事件与手势:从触摸到组合手势
ui·华为·性能优化·harmonyos
星栖与芯1 小时前
LiteOS-M 切换汇编逐行图解(5):HalPendSV 任务切换的现场搬运(五阶段逐行)
汇编·stm32·嵌入式硬件·harmonyos
马剑威(威哥爱编程)2 小时前
【共创稿事节】HarmonyOS 7 数字身份 DID 实战:TEE 颁发、本人同意、最小化出示
华为·harmonyos
贾伟康2 小时前
【HarmonyOS 7新能力|039】冷启网络预建链工程封装:把接入逻辑放进可维护的分层结构
性能优化·harmonyos·arkts·软件架构·网络优化
星栖与芯11 小时前
LiteOS-M 切换汇编逐行图解(4):中断三件套与 HalTaskSchedule 触发
汇编·stm32·单片机·嵌入式硬件·harmonyos
李游Leo14 小时前
定位功能“偶尔失效“怎么查:HarmonyOS Location Kit 权限、订阅与地理围栏实践
harmonyos
庆登登登18 小时前
nvm 鸿蒙 PC 适配全记录:从 Shell Function 到 HNP 原生交付
华为·harmonyos
不羁的木木18 小时前
给鸿蒙 App 增加用系统应用打开文件的能力 —— open_app_file 的鸿蒙使用指南
flutter·harmonyos
李游Leo18 小时前
HarmonyOS 7 系统能力深度实战 02:用模块化对象开放应用内部能力
harmonyos