【OpenHarmony/HarmonyOs 】安全打开网页:Axios HTTPS 拦截、URL 检测与 ArkWeb 容器

前言

导航类应用最重要的边界是外部链接。一个 URL 从用户输入到显示网页,至少要经过协议规范化、网络策略、安全判断、路由传参和 WebView 加载。任何入口绕过其中一步,都可能形成风险。本文给出 LinkOS 链界当前实现,并说明生产环境还需要补齐什么。🔐

一、第一层:网络请求统一强制 HTTPS

项目基于 @ohos/axios 创建共享实例,并设置 8 秒超时:

private static readonly TIMEOUT = 8000;

HttpUtil.instance = axios.create({
  timeout: HttpUtil.TIMEOUT,
  headers: { 'Content-Type': 'application/json' }
});

请求拦截器阻止非 HTTPS 地址:

HttpUtil.instance.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {
    if (config.url && !config.url.startsWith('https://')) {
      console.error('[HttpUtil] Non-HTTPS blocked:', config.url);
      return Promise.reject(new Error('Only HTTPS requests are allowed'));
    }
    return config;
  }
);

统一实例的优势在于:搜索建议、天气、云函数等请求都会自动遵守相同策略。以后增加 Token、Trace ID、统一错误映射,也只需改一处。

二、第二层:区分超时与断网

HttpUtil.instance.interceptors.response.use(
  response => response,
  (error: AxiosError) => {
    if (error.code === 'ECONNABORTED') {
      console.error('[HttpUtil] Request timeout');
    } else if (error.message?.includes('Network Error')) {
      console.error('[HttpUtil] Network error');
    }
    return Promise.reject(error);
  }
);

网络错误不应全部显示“请求失败”。超时、无网络、证书错误、401 与服务器 500 对用户和排查者的意义不同。建议进一步定义领域错误,例如 TIMEOUTOFFLINEAUTH_EXPIRED,页面只负责将错误映射为合适提示。

三、第三层:打开前执行 URL 安全检测

private async openUrl(url: string): Promise<void> {
  const safe = await SecurityUtil.checkUrlSafety(url);
  if (!safe) {
    AlertDialog.show({
      title: '风险提示',
      message: `该网站 (${url}) 可能包含恶意内容,已拦截。`,
      confirm: { value: '知道了', action: () => {} }
    });
    return;
  }

  router.pushUrl({
    url: 'pages/v2/WebViewPage',
    params: { url }
  });
}

项目中的 SecurityUtil 目前是模拟实现:要求 URL 以 HTTPS 开头、提取 hostname,并对本地黑名单检查。它适合演示调用链,但不能等同于真实安全能力。

生产环境至少要防范:

  • 钓鱼与恶意域名;
  • Unicode 相似字符域名;
  • 重定向后跳到 HTTP 或危险 Scheme;
  • javascript:file: 等不期望协议;
  • 证书异常与中间人攻击;
  • URL 中携带的敏感 Token 被记录到日志。

四、域名匹配不能使用简单 includes

示例中黑名单使用 hostname.includes(domain)。这会让 notmalicious.com 也可能命中 malicious.com。更严谨的域名边界判断是:

function matchesDomain(host: string, domain: string): boolean {
  return host === domain || host.endsWith(`.${domain}`);
}

此外,正则提取 hostname 适合受控输入;面对完整 URL 标准时,应优先使用平台提供的 URI/URL 解析能力,避免自行解析遗漏边界。

五、ArkWeb 页面接收路由参数

@Entry
@Component
struct WebViewPage {
  controller: webview.WebviewController = new webview.WebviewController();
  @State url: string = '';
  @State title: string = '加载中...';
  @State progress: number = 0;

  aboutToAppear() {
    const params = router.getParams() as Record<string, string>;
    if (params && params['url']) {
      this.url = params['url'];
    }
  }
}

WebviewController 用于后续控制前进、后退、刷新等行为;路由参数只负责传递目标地址。页面进入时仍建议再次验证参数,实现“纵深防御”,不要完全相信上一个页面。

六、标题和进度构成基础浏览体验

Web({ src: this.url, controller: this.controller })
  .width('100%')
  .layoutWeight(1)
  .onTitleReceive(event => {
    if (event) this.title = event.title;
  })
  .onProgressChange(event => {
    if (event) this.progress = event.newProgress;
  });

加载进度小于 100 时展示 Progress,收到网页标题后更新顶部栏。即使只是一个最小 Web 容器,这两个反馈也能显著降低用户对“页面卡住”的误判。

完整浏览器容器还应实现:

  • WebView 内部返回历史,历史为空再退出页面;
  • 加载失败页与重试按钮;
  • 顶部域名显示和证书风险提示;
  • 下载、文件选择、权限请求策略;
  • 新窗口与外部 Scheme 拦截;
  • 页面销毁时资源释放;
  • JavaScript Bridge 最小权限和来源校验。

七、不要把 API Key 放进客户端

项目计划接入 AI 服务,这里必须强调:HAP 最终运行在用户设备上,任何硬编码 Key 都能被提取。正确链路应是:

HarmonyOS 客户端
   ↓ 用户身份令牌 + HTTPS
AGC Cloud Function
    服务端保管密钥、限流、审计
第三方 AI API

客户端只接触自己的短期身份凭证,不接触供应商密钥。云函数还应校验用户、限制请求频率并过滤日志中的敏感字段。

八、总结

网页安全不是一个 startsWith('https://') 就能完成,而是多层防线:输入规范化、统一请求拦截、域名安全服务、重定向检查、WebView 导航拦截与服务端密钥隔离。当前项目已经搭出完整链路骨架,下一阶段应把模拟检测替换为真实安全服务,并补齐 WebView 内部导航策略。🛡️

img

更多推荐