云计算百科
云计算领域专业知识百科平台

【鸿蒙心迹】端云协同踩坑录:HarmonyOS 7 应用调后端 API,我在证书、鉴权、弱网上交的学费


【鸿蒙心迹】端云协同踩坑录:HarmonyOS 7 应用调后端 API,我在证书、鉴权、弱网上交的学费


免责声明:本文为个人开发笔记,环境为 HarmonyOS 7.x(API 26)/ DevEco Studio 5.x,后端为 Nginx + 自建 API。不同 API 版本行为可能有差异,请以官方文档为准。

起因:本地能跑,真机白屏

App 业务逻辑写完,接口一接就翻车:Previewer 里 mock 数据一切正常,真机上一片空白。服务端 Nginx access log 里连一条请求都没有——说明请求根本没发出去。

这类"端上没发、服务端没收"的问题,我把排查顺序固定下来了:网络权限 → 网络策略 → 证书 → 鉴权与参数。我这次正好四个全踩了一遍。

坑一:请求没发出去,日志却只字不提权限

现象:http.createHttp().request() 直接进 err 回调,控制台信息含糊。

根因:module.json5 里没声明网络权限。这是鸿蒙和 Android 第一个差异——Android 的 INTERNET 是普通权限(声明即给),鸿蒙这边必须显式声明:

// entry/src/main/module.json5
{
"module": {
"requestPermissions": [
{ "name": "ohos.permission.INTERNET" }
]
}
}

判断技巧:先看服务端有没有收到请求。服务端无日志 = 端上问题(权限/网络/崩溃);服务端有日志 = 服务端问题(鉴权/参数/超时)。这一条能省掉一半排查时间。

坑二:明文 HTTP 被拦,以及那两个默认打开的开关

测试环境接口是 http://,真机请求全挂。

解法两条路,我推荐第二条:

方案 A(临时放行):在 entry/src/main/resources/base/profile/ 下建 network_config.json,放行指定调试域名:

// entry/src/main/resources/base/profile/network_config.json
{
"network-security-config": {
"base-config": {
"cleartextTrafficPermitted": false // 生产环境禁止明文
},
"domain-config": [
{
"domains": [{ "include-subdomains": true, "name": "test.example.com" }],
"cleartextTrafficPermitted": true // 仅调试域名放行
}
]
},
"trust-global-user-ca": false, // 不信任 MDM/设备管理员安装的 CA,默认 true
"trust-current-user-ca": false // 不信任当前用户安装的 CA,默认 true
}

两个细节特别容易踩:

  • trust-global-user-ca 和 trust-current-user-ca 是顶层字段,与 network-security-config 平级、在它外面。我一开始塞进了 base-config 里,配置完全不生效,排查了半天——这两个字段默认都是 true,也就是默认信任用户自行安装的 CA 证书。设备一旦被装上恶意证书,HTTPS 流量可能被解密,生产环境务必显式关掉。
  • 引用要写在 module.json5 的 metadata 节点里,不是顶层字段:
  • {
    "module": {
    "metadata": [
    {
    "name": "ohos.network.security.config",
    "resource": "$profile:network_config"
    }
    ]
    }
    }

    配置优先级是 component-config > domain-config > base-config,所以"base 全禁 + domain 单独放行"成立。改完必须重新编译打包,热重载不一定生效。

    方案 B(推荐):给测试环境直接上 HTTPS。反正正式环境迟早要上,早一天上就少一次"开发能跑、上线就炸"。

    抓包相关的副作用:上面两个开关关掉后,Charles / mitmproxy 就抓不到明文了。调试期用临时配置放行,上架前务必改回来。

    坑三:SSL 握手失败——坑在中间证书

    正式环境切 HTTPS 后真机报 SSL 握手失败,但浏览器访问同一域名完全正常。

    根因:证书链不完整——服务端只配了站点证书,没配中间证书。浏览器会自动补全链(AIA 追链),而移动端的 TLS 实现通常不做这个动作。

    这个坑我在服务端文章里写过,没想到在鸿蒙端上又遇到一次。检查与修复:

    # 看服务端实际发出的链
    openssl s_client -connect api.example.com:443 -servername api.example.com

    # 正确配置:站点证书 + 中间证书合并为一个 fullchain
    cat your_domain.crt intermediate.crt > fullchain.pem

    Nginx 里 ssl_certificate 要指向 fullchain.pem,不是单独的站点证书。这类问题在服务端补全证书链即可,不需要在客户端动信任锚。

    顺带提醒:证书有效期在往 45 天甚至更短演进,端上无感知,但服务端必须把到期监控做到位——按剩余比例告警(比如剩余 30% 就告警),别等过期了全线 502。

    坑四:鉴权别散落在业务页面里——用拦截器统一注入

    现象:登录接口通了之后,我一开始在每个业务请求里手动塞 Authorization 头。十几个接口写完,Token 刷新逻辑已经在四个地方各写了一遍,改一次要找四处。

    正确做法:请求层统一封装,鉴权头、日志、重试全放拦截器里,业务代码只管传 URL 和参数:

    // 统一在拦截器里注入,业务侧无感
    sessionConfig.interceptors = [{
    async onRequest(req: rcp.Request) {
    req.headers = {
    …req.headers,
    Authorization: `Bearer ${await getToken()}`,
    'X-Request-ID': genId() // 贯穿端云的 requestId
    };
    return req;
    }
    }]

    401 处理建议:在拦截器的 onError 里统一拦 401,触发一次刷新后重放原请求;刷新失败再跳登录。别让刷新逻辑散落在业务页面里。

    凭据存储提示:Token 不要直接落 Preferences——它是明文 KV,安全性靠沙箱隔离,挡不住 root 设备和备份提取。正确做法是走系统安全能力:用 HUKS 生成 AES-256-GCM 密钥,加密后写入 el2 加密目录,取用时再解密。这块站内已有专门文章,不重复展开。

    坑五:从 http 模块迁到 RCP 的三个注意点

    老写法是每个请求 createHttp → request → destroy 三件套,功能没毛病,但有两个隐患:destroy 时机不好把握(页面切走早了容易崩)、每个请求都新建连接(列表页连拉十几个接口时明显慢)。

    迁到 RCP(Remote Communication Kit) 之后:

    1. Session 别到处 new:有同事在每个页面都建 Session,连接根本没复用。正确做法是应用启动时建一个全局单例:

    import { rcp } from '@kit.RemoteCommunicationKit';

    const sessionConfig: rcp.SessionConfiguration = {
    // 用于指定与会话关联的 HTTP 请求的配置
    requestConfiguration: {
    transfer: {
    // 超时时间参数设置
    timeout: {
    connectMs: 5000, // 连接超时,单位毫秒,默认值 60000
    transferMs: 10000 // 传输超时,单位毫秒,默认值 60000
    }
    }
    }
    };

    const session = rcp.createSession(sessionConfig);

    2. 超时一定要自己配:默认值是 60000 毫秒,也就是弱网下要等整整一分钟才报错,用户感知就是"转圈圈"。一般连接 5 秒、传输 10 秒够用。

    3. 自签证书握手失败:测试环境用自签证书时,RCP 默认校验直接失败,开发期可以在 security 配置里临时跳过:

    security: {
    remoteValidation: 'skip', // 开发期临时用;上架前必须改回系统校验
    tlsOptions: { tlsVersion: 'TlsV1.2' }
    }

    重试建议:只对幂等请求(GET)做指数退避重试,POST 谨慎(可能重复下单);页面退出时取消未完成请求,否则容易内存泄漏。

    端云联调:用一个 requestId 把两头串起来

    这是我觉得最值得沉淀的一条经验。

    做法:App 每次请求生成一个 requestId,放进请求头;服务端 Nginx 和 API 都把它写进日志:

    # Nginx:把端上带来的 requestId 记录到 access log
    log_format main '$remote_addr – $request_time – "$http_x_request_id" '
    '"$request" $status';

    端上排障时,从 hilog 拿到 requestId,直接去服务端日志里 grep:

    grep 'a1b2c3d4' /var/log/nginx/access.log

    一次定位,不用再靠时间戳猜。 弱网、重试、超时这类问题,有没有 requestId 完全是两种排障效率。

    抓包提醒:真机抓 HTTPS 需要装 CA 证书,而上面提到的 trust-current-user-ca 关掉后就抓不到明文了。判断依据:能抓到 CONNECT 但解不出明文 = CA 没被信任,不是代理配错了。

    问题速查表

    现象大概率原因处理
    请求直接失败,服务端无日志 未声明 INTERNET 权限 加 requestPermissions
    只有 http 接口失败 明文流量被拦 配 network_config 或改 https
    改完配置没生效 热重载未生效 重新编译打包
    浏览器正常、App SSL 失败 中间证书缺失 配 fullchain 并 reload
    切 HTTPS 后全线挂 证书过期 查有效期,补到期监控
    抓包只见 CONNECT 用户 CA 未信任 检查 trust-* 配置
    地铁/弱网转圈 超时默认值 + 无退避重试 显式设超时,GET 做指数退避
    页面退出后偶发崩溃 请求未取消 取消请求 + 挂生命周期
    排障靠猜 端云日志无关联 加 requestId 贯穿

    FAQ

    Q1:Previewer 里能调网络接口吗?
    不能,Previewer 不提供网络等系统能力。涉及网络的调试必须上真机或模拟器。

    Q2:一定要上 HTTPS 吗?
    正式环境基本是硬性要求。即便平台不强制,明文传输 Token 的安全风险也不值得冒。

    Q3:证书问题怎么定位是端还是服务端?
    浏览器正常、App 失败 → 大概率证书链或系统信任问题;浏览器也失败 → 服务端配置问题。再配合 openssl s_client 看实际发出的链。

    Q4:Token 该存在哪里?
    别用 Preferences。走系统安全能力(HUKS 生成密钥 + 加密写入 el2 目录,或使用 Asset Store Kit)。业务里取用时再解密,不要常驻全局状态。

    Q5:刷新 Token 的逻辑放哪一层?
    统一在请求拦截器的错误回调里处理,业务页面不感知。刷新成功重放原请求,刷新失败再跳登录。

    Q6:重试会不会造成重复提交?
    会。所以只给幂等请求加重试;写操作要么不自动重试,要么带幂等键由服务端去重。

    Q7:老项目要不要从 http 迁到 RCP?
    新项目直接用 RCP。老项目不急的话可以按模块慢慢换,改动面主要在 Session 单例改造和超时参数迁移两块,风险可控。

    写在最后

    这个 App 从"接口调不通"到稳定上线,真正卡时间的技术难点不在 UI,而在端和云之间那条链路:网络权限、明文策略、证书链、鉴权注入、超时重试、日志关联。而这些恰好是后端/运维视角的强项。

    如果你也在做鸿蒙适配,建议第一周就把这四件事定死:网络安全配置、凭据存储方案、请求层拦截器封装、requestId 贯穿。后面接多少接口都不会乱。

    感兴趣可以关注一下,我们下期见。


    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【鸿蒙心迹】端云协同踩坑录:HarmonyOS 7 应用调后端 API,我在证书、鉴权、弱网上交的学费
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!