在鸿蒙 WebView 混合架构中,IPC(进程间通信)或 JSBridge 是 Web 页面与原生侧交互的核心通道。为了防止恶意页面(如被注入恶意脚本的页面或第三方加载的页面)伪造请求、窃取数据或执行未授权操作,必须建立一套严格的安全防御机制。核心思路是将原生侧视为安全边界,对来自 Web 的所有请求进行身份认证、来源校验和权限控制

以下是具体的安全策略与实现方案。

一、 安全威胁模型与防护策略对比

恶意页面伪造请求的常见方式包括:篡改注入的 JS 脚本、利用跨站脚本(XSS)漏洞、加载不受控的第三方页面等。针对这些威胁,可采取多层次的安全防护。

安全维度 核心威胁 防护策略 实施要点
请求来源验证 非授权域名或本地文件加载的页面调用 JSBridge 域名/协议白名单校验 在原生侧拦截并验证请求的 URL 来源
身份认证 已加载的合法页面内,恶意脚本冒用合法身份发起请求 Token/签名动态注入与验证 原生侧在页面加载时注入一次性 Token,每次请求需携带并验证
参数安全 请求参数被篡改,导致越权操作(如访问他人数据) 参数签名与完整性校验 对请求参数生成签名,原生侧验签确保数据未被篡改
权限隔离 恶意请求试图调用高敏感接口(如文件读写、通讯录) 基于角色的接口访问控制 (RBAC) 根据 Web 页面的业务角色动态分配可调用的 JSBridge 方法集

二、 核心安全实现方案

1. 严格校验 WebView 加载来源(第一道防线)

在创建 WebView 或处理请求前,必须验证当前加载页面的 URL 是否在可信白名单内。

// ArkTS 侧 WebViewController 示例
import web_webview from '@ohos.web.webview';
import { BusinessError } from '@ohos.base';

@Entry
@Component
struct SecureWebView {
  controller: web_webview.WebviewController = new web_webview.WebviewController();
  // 定义可信域名白名单
  private trustedDomains: string[] = [
    'https://your-legitimate-domain.com',
    'file://resource/rawfile/', // 允许加载本地安全资源
  ];

  aboutToAppear() {
    // 监听页面加载开始,进行来源校验
    this.controller.onLoadStart((event) => {
      const url = event?.url;
      if (!url || !this.isUrlTrusted(url)) {
        console.error(`安全拦截:试图加载非授信源 ${url}`);
        // 可选择停止加载或跳转到错误页
        this.controller.stopLoading();
        // 跳转到本地安全提示页
        this.controller.loadUrl('file://resource/rawfile/error.html');
      } else {
        console.info(`允许加载授信源:${url}`);
      }
    });
  }

  // 校验URL是否可信
  private isUrlTrusted(url: string): boolean {
    return this.trustedDomains.some(domain => url.startsWith(domain));
  }

  build() {
    Column() {
      Web({ src: 'https://your-legitimate-domain.com/index.html', controller: this.controller })
        .width('100%')
        .height('100%')
        .javaScriptAccess(true) // 开启JS交互
        .onInterceptRequest((event) => {
          // 可选:进一步拦截所有资源请求(如图片、API),进行更细粒度的校验
          return this.handleInterceptRequest(event);
        })
    }
  }

  private handleInterceptRequest(event: any): boolean {
    // 示例:拦截所有请求,确保其来源或目标为授信域
    const requestUrl = event?.request?.url;
    if (requestUrl && !this.isUrlTrusted(requestUrl)) {
      console.warn(`拦截非授信资源请求:${requestUrl}`);
      return true; // true 表示拦截该请求
    }
    return false; // false 表示允许请求通过
  }
}

代码解析:此代码在页面加载开始(onLoadStart)时进行第一层域名校验,防止加载恶意首页。同时,通过 onInterceptRequest 可以拦截页面内的所有子资源请求(如 XHR/Fetch、图片),实现网络层面的访问控制 。

2. 动态令牌注入与验证(第二道防线)

对于授信页面,原生侧在页面加载完成后注入一个临时的、唯一的令牌(Token)。所有后续的 JSBridge 调用都必须携带此令牌,原生侧验证令牌有效性后才处理请求。

步骤一:原生侧注入令牌

// 在 ArkTS 组件中,监听页面加载完成
private setupTokenInjection() {
  this.controller.onPageFinish((url: string) => {
    if (this.isUrlTrusted(url)) {
      // 生成一个临时令牌(实际应用中应更复杂,如JWT)
      const token = this.generateSecureToken();
      // 将令牌注入到 Web 页面的全局环境
      const jsCode = `
        (function() {
          window.__NATIVE_TOKEN__ = '${token}';
          console.log('安全令牌已注入');
        })();
      `;
      this.controller.runJavaScript(jsCode, (err: BusinessError) => {
        if (err) {
          console.error('令牌注入失败:', JSON.stringify(err));
        }
      });
    }
  });
}

private generateSecureToken(): string {
  // 实际开发中应使用加密库生成,此处为示例
  return 'token_' + Date.now() + '_' + Math.random().toString(36).substr(2);
}

步骤二:JSBridge 接口统一验证令牌
假设我们通过 runJavaScriptonMessage 方式暴露一个 callNative 方法给 Web。

// Web 页面端 JS:封装安全的调用函数
window.callSecureNative = function(method: string, params: any): Promise<any> {
  return new Promise((resolve, reject) => {
    // 获取原生注入的令牌
    const token = window.__NATIVE_TOKEN__;
    if (!token) {
      reject(new Error('安全令牌未就绪,请等待页面加载完成'));
      return;
    }

    // 构造请求对象,包含令牌
    const request = {
      method: method,
      params: params,
      timestamp: Date.now(),
      token: token
    };

    // 假设通过 postMessage 或特定函数与原生通信
    // 这里以假设的全局桥接对象为例
    if (window.NativeBridge && window.NativeBridge.postMessage) {
      window.NativeBridge.postMessage(JSON.stringify(request), (response) => {
        // 处理原生返回
        const resp = JSON.parse(response);
        if (resp.code === 200) {
          resolve(resp.data);
        } else {
          reject(new Error(resp.message || '调用失败'));
        }
      });
    } else {
      reject(new Error('原生桥接不可用'));
    }
  });
};

// 使用示例:调用获取位置的接口
window.callSecureNative('getLocation', { highAccuracy: true })
  .then(location => console.log('位置:', location))
  .catch(err => console.error('获取失败:', err));

步骤三:原生侧验证请求
在原生侧接收消息的地方,验证令牌。

// ArkTS 侧:监听来自 Web 的消息
private setupMessageHandler() {
  // 假设通过某种方式(如 onMessage)接收消息
  this.controller.onMessage((event) => {
    try {
      const request = JSON.parse(event?.data);
      // 1. 验证令牌是否存在且有效
      if (!request.token || !this.isTokenValid(request.token)) {
        this.sendErrorResponse(event, '非法令牌或令牌已失效', 403);
        return;
      }
      // 2. 可选:验证时间戳,防止重放攻击
      if (Date.now() - request.timestamp > 5 * 60 * 1000) { // 5分钟有效期
        this.sendErrorResponse(event, '请求已过期', 408);
        return;
      }
      // 3. 令牌有效,处理业务请求
      this.handleBridgeMethod(request.method, request.params, event);
    } catch (error) {
      this.sendErrorResponse(event, '请求格式错误', 400);
    }
  });
}

private isTokenValid(token: string): boolean {
  // 实现令牌验证逻辑,例如检查是否在已签发的令牌列表中,是否过期等
  // 此处为示例,实际应结合缓存或数据库
  return token.startsWith('token_'); // 简单示例
}

private sendErrorResponse(event: any, message: string, code: number) {
  const response = JSON.stringify({ code: code, message: message });
  // 通过相应方式将错误信息传回 Web
  this.controller.postMessage(event?.origin, response);
}

3. 接口级权限控制(RBAC)

根据 Web 页面的业务角色,限制其可调用的 JSBridge 方法。例如,一个普通的资讯页面不应该能调用“发送短信”或“读取通讯录”的接口。

// ArkTS 侧:维护接口-角色映射
private methodPermissionMap: Map<string, string[]> = new Map([
  ['getLocation', ['user', 'admin']], // 用户和管理员可调用
  ['readContacts', ['admin']], // 仅管理员可调用
  ['getDeviceInfo', ['user', 'admin', 'guest']], // 所有角色可调用
]);

private handleBridgeMethod(method: string, params: any, event: any) {
  // 1. 获取当前页面的角色(可从注入的令牌中解析,或根据URL判断)
  const currentPageRole = this.getRoleFromTokenOrUrl(event?.url); // 假设实现此方法

  // 2. 检查该角色是否有权限调用此方法
  const allowedRoles = this.methodPermissionMap.get(method);
  if (!allowedRoles || !allowedRoles.includes(currentPageRole)) {
    this.sendErrorResponse(event, `角色 ${currentPageRole} 无权限调用方法 ${method}`, 403);
    return;
  }

  // 3. 有权限,执行对应方法
  switch (method) {
    case 'getLocation':
      this.handleGetLocation(params, event);
      break;
    // ... 其他方法
    default:
      this.sendErrorResponse(event, `未知方法: ${method}`, 404);
  }
}

三、 总结与最佳实践

  1. 纵深防御:不要依赖单一安全措施。应组合使用来源校验动态令牌接口权限控制,构建多层防御体系。
  2. 最小权限原则:仅为 Web 页面暴露其完成功能所必需的最少 JSBridge 接口,并为每个接口分配最小必要权限。
  3. 令牌生命周期管理:注入的令牌应设置较短的过期时间(如 30 分钟),并在用户退出登录或应用切换到后台时主动失效。
  4. 敏感操作二次确认:对于特别敏感的操作(如支付、删除数据),即使请求通过了所有校验,也应在原生侧弹出系统级别的确认对话框,由用户最终授权。
  5. 定期安全审计:定期检查 JSBridge 的调用日志,监控异常调用模式,及时发现潜在的攻击行为。

通过上述方案,可以极大程度地防止鸿蒙 WebView 中恶意页面伪造 IPC/JSBridge 请求,确保混合应用的安全边界牢固可靠 。​​​​​​

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐