业务接入端侧风控,最容易犯的错误是把“设备返回了一个字符串”当成“交易可以放行”。放行前需要确认结果来自可信能力,针对当前业务请求产生,仍在有效期内,并且没有被另一次操作复用。

星盾机密风控适合单独建立沙箱实验,无需把支付场景塞进知识图鉴。一份测试订单用于说明调用边界。API 26 SDK 中可核对到 riskControlEngine 的风险因子导入、风险结果获取及 JWS 结果定义;具体策略开通与服务端验证材料仍需要对应业务接入条件。

先从服务端发起挑战

客户端选择测试订单后,服务端生成一次性挑战,把它与订单编号、用户会话、操作类型和有效时间关联。设备收到挑战后请求风险结果,再把原始结果送回服务端。客户端不自行解释几个字段后决定放行。

SDK 对 policyName 和 nonce 有长度约束,分别为 1~255 字节、16~66 字节。字节长度与字符数量不总相等。生成挑战时应使用安全随机源和约定编码,校验最终编码后的字节长度,避免中文或特殊编码导致参数错误。

下面是业务层的数据契约,不代表系统接口签名。

interface RiskChallenge {
  challengeId: string;
  orderId: string;
  action: 'confirm-test-order';
  nonce: string;
  expiresAt: number;
}
interface RiskSubmission {
  challengeId: string;
  signedResult: string;
}

订单内容改变以后,原挑战应失效。否则用户确认的内容与风险判断关联的内容可能不同。测试订单不连接实际资金操作,也不记录真实账户敏感数据。

JWS 解码不等于验证

JWS 可以携带签名保护的数据,但把它按分隔符拆开并解码,只能看到内容。服务端还需要按照官方信任材料和算法要求验证签名,检查挑战绑定、有效期及业务上下文。

不要信任令牌自行声明的任意验证地址或算法,也不要用“字段存在”代替真实性检查。具体证书链、密钥轮换及声明名称必须依据接入文档实现,无法脱离这些材料给出通用验签函数。

验签成功后,风险结果仍然是业务决策的输入之一。策略不可用、设备不支持或结果过期,应进入明确的人工确认、补充验证或拒绝流程;不能统一当作低风险。

因子最小化与请求生命周期

导入风险因子前,应先列明来源、用途、保存期限与必要性。SDK 提供能力不代表应用可以任意采集通讯录、位置或其他无关信息。实验只使用接入规范允许的最小测试因子。

同一个挑战只允许一个有效决策。重复点击时复用正在进行的业务任务;客户端超时后如果旧回调到达,也要检查当前 challengeId。服务端对挑战的消费采用原子操作,避免两个并发请求都通过“尚未使用”的检查。

日志记录阶段、错误码和随机请求标识即可,不输出完整签名结果、原始因子及用户身份内容。测试失败时需要能定位是能力查询、参数检查、服务调用还是服务端验证失败。

沙箱实验如何证明边界有效

正向用例使用合法挑战和已配置策略,完成设备调用、服务端验签及一次性消费。反向用例依次替换 nonce、修改测试订单、延迟到过期、再次提交同一结果以及切换到不支持能力的设备。

预期结果应写在运行前。重放用例必须被服务端拒绝;客户端按钮变灰并不能证明后端防重放有效。修改内容后签名验证失败,也不能自动证明业务挑战绑定正确,两类检查需要分别覆盖。

遇到能力不支持或策略未开通,要保存错误阶段,停止把实验结果解释为风控能力准确率。风险识别效果还需要具有合法来源和明确标签的数据集,几次手工点击无法估计误报和漏报。

沙箱闭环需要哪些材料

完整 Demo 应交付最小客户端、沙箱验证服务、挑战状态存储和脱敏测试记录。运行记录可以展示流程和错误样例,但不能暴露信任密钥或完整安全令牌。现有实现覆盖调用与信任边界,风控效果仍需要已标注的合法样本和真实策略结果。

把验证失败与业务拒绝分别返回

判定阶段失败时的含义后续路径
信任与签名结果不可接受不消费为有效风控结果
挑战及订单绑定结果不属于本次请求要求重新发起
策略结果真实结果未满足业务条件按既定沙箱策略处理

客户端判断简单,但无法独立建立服务端挑战信任;服务端验证多一段链路,却能统一一次性消费和业务决策。实施先完成挑战存储与重放用例,再配置测试策略,最后接设备结果。没有正式验证材料时,流程测试只能使用明确标注的测试信任域,不宣称已验证平台结果。

参考:华为开发者能力介绍;接口约束核对自 API 26 SDK 的 riskControlEngine 类型声明,具体策略与信任材料以业务接入文档为准。

风险因子与 JWS 结果的调用顺序

同一次检测先导入因子,再使用相同 nonce 请求已配置的策略。异步互斥避免重复点击同时发起两套流程;导入失败不进入检测,finally 解除忙碌状态。

客户端只把 JWS 当成不透明响应交给后续处理,演示界面仅显示长度。服务端需要按受信证书链与算法验证签名,再解释策略结果;不能把成功取得字符串直接当作风控通过。

export class RiskProbe {
  private busy: boolean = false;
  async detect(policy: string, nonce: string): Promise<string> {
    InputRules.required(policy, 'policy');
    InputRules.required(nonce, 'server nonce');
    if (this.busy) { throw new Error('detection already running'); }
    this.busy = true;
    try {
      await riskControlEngine.importRiskFactors({ nonce: nonce,
        appFactorData: [{ factorName: 'lab_scene', factorValue: 'exhibit_preview' }] });
      const response: riskControlEngine.RiskControlDetectionResponse =
        await riskControlEngine.getRiskControlResult({ policyName: policy, nonce: nonce });
      return response.result;
    } finally { this.busy = false; }
  }
}

辅助校验与边界处理使用以下实现:

export class InputRules {
  static required(value: string, name: string, max: number = 256): string {
    if (typeof value !== 'string' || value.trim().length === 0 || value.length > max) {
      throw new Error(name + ' is missing or too long');
    }
    return value.trim();
  }
  static hex(value: string): Uint8Array {
    if (!/^[0-9a-fA-F]{64}$/.test(value)) { throw new Error('challenge must be 32 bytes of hex'); }
    const bytes: Uint8Array = new Uint8Array(32);
    for (let i: number = 0; i < bytes.length; i++) { bytes[i] = parseInt(value.substring(i * 2, i * 2 + 2), 16); }
    return bytes;
  }
}

用失败短路和互斥检查核对源码

合法策略和因子配置、服务端 nonce、证书链信任及 JWS 验签。

检查层当前处理不能替代的结果
编译API 26 工程进行类型检查与打包目标设备支持
逻辑校验输入、状态或资源释放边界平台服务真实返回
联调按上面的输入和条件逐步执行不能以按钮文案代替核心结果

riskControlEngine 与 JWS 结果的版本约束,以 HarmonyOS 官方文档中心、当前 SDK 声明和业务接入文档为准。

风控请求的先后顺序和忙碌恢复

导入因子失败时不调用检测,同一时刻只执行一次检测,失败后解除忙碌状态。测试关心客户端编排;真实 JWS 内容仍由可信服务端校验。宿主测试直接载入 RiskProbe,仅以替身代替 riskControlEngine;load 与 test 属于测试框架。

test('risk: serial factor import and detection; unlock after failure', async () => {
    const order = [];
    let reject = true;
    const C = load('services/RiskProbe', {
        '@kit.DeviceSecurityKit': {
            riskControlEngine: {
                importRiskFactors: async () => {
                    order.push('import');
                    if (reject)
                        throw Error('reject');
                }, getRiskControlResult: async () => {
                    order.push('detect');
                    return {
                        result: 'opaque-jws'
                    };
                }
            }
        }
    }).RiskProbe;
    const x = new C();
    await assert.rejects(x.detect('p', 'n'));
    reject = false;
    assert.equal(await x.detect('p', 'n'), 'opaque-jws');
    assert.deepEqual(order, ['import', 'import', 'detect']);
});
test('risk: duplicate concurrent request rejected before a second import', async () => {
    let resolve, calls = 0;
    const pending = new Promise(r => resolve = r);
    const C = load('services/RiskProbe', {
        '@kit.DeviceSecurityKit': {
            riskControlEngine: {
                importRiskFactors: () => {
                    calls++;
                    return pending;
                }, getRiskControlResult: async () => ({
                    result: 'jws'
                })
            }
        }
    }).RiskProbe;
    const x = new C(), first = x.detect('p', 'n');
    await assert.rejects(x.detect('p', 'n'), /already/);
    resolve();
    await first;
    assert.equal(calls, 1);
});
操作或边界应检查的结果
输入不满足前提不调用后续平台操作
平台拒绝或抛错保留错误,不生成成功状态
再次进入或重试检查旧状态不会污染新任务
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐