HarmonyOS鸿蒙Next中arkweb是否可以实现透明加解密,让加载的网页无感?

HarmonyOS鸿蒙Next中arkweb是否可以实现透明加解密,让加载的网页无感? 比如我现在有一个已经实现功能的前后端页面:前端页面明文发送账号口令给后端,后端验证后响应数据给前端页面然后展示。

我现在希望把前端页面放到鸿蒙arkweb中,我要拦截这个网页的所有请求,比如/api/login,/api/register等。

拦截到将要发送的,就把body中的数据加密,然后再发。

拦截到相应给页面的,就把body中的数据解密,然后给页面。

这个加解密的函数鸿蒙native端,前端网页,后端都有对应实现。

我这个功能请问目前可以实现吗?

文档中提到

Web组件是否支持拦截Ajax原始响应?

不支持。Web组件目前仅提供网络请求的拦截方法,无法拦截请求的响应。然而,可以通过onInterceptRequest()回调或WebSchemeHandler机制自定义响应。

无法拦截响应请求那该怎么用onInterceptRequest()回调或WebSchemeHandler机制自定义响应实现?

可以的话麻烦给一个简单的实现demo,比如发送时把账号密码都加上首部和尾部都加上###做“加密”,解密时前后的###去掉做“解密”,这个该如何实现?


更多关于HarmonyOS鸿蒙Next中arkweb是否可以实现透明加解密,让加载的网页无感?的实战教程也可以访问 https://www.itying.com/category-93-b0.html

12 回复

开发者您好,您可参考以下方案:

可以通过setWebSchemeHandler拦截网页中所有请求,包含axios请求,拦截后进行加解密操作,通过rcp或则http重新构造请求,请求服务端,或则响应后进行解密,返回给网页。示例代码可参考如下:

import { WebNetErrorList, webview } from '@kit.ArkWeb';
import { BusinessError } from '@kit.BasicServicesKit';
import { JSON, util } from '@kit.ArkTS';
import { rcp } from '@kit.RemoteCommunicationKit';


@Entry
@Component
struct WebComponent {
  controller: webview.WebviewController = new webview.WebviewController();
  schemeHandler: webview.WebSchemeHandler = new webview.WebSchemeHandler();
  htmlData: string = '<html><body bgcolor="white">Source:<pre>source</pre></body></html>';

  build() {
    Column() {
      Web({
        // 使用时请替换为真实url
        src: 'https://www.example.com',
        controller: this.controller
      })
        .onControllerAttached(() => {
          try {
            this.schemeHandler.onRequestStart((request: webview.WebSchemeHandlerRequest,
              resourceHandler: webview.WebResourceHandler) => {
              console.info('[schemeHandler] onRequestStart');

              // 判断哪些请求不拦截,可以根据url或其他条件进行判断
              if (!request.getRequestUrl().startsWith('https://www.example.com')) {
                return false;
              }

              // 对拦截请求进行处理
              this.interceptRequest(request, resourceHandler);
              return true;
            });

            this.schemeHandler.onRequestStop((request: webview.WebSchemeHandlerRequest) => {
              console.info('[schemeHandler] onRequestStop, url:' + request.getRequestUrl());
            });

            this.controller.setWebSchemeHandler('https', this.schemeHandler);
          } catch (error) {
            console.error(`ErrorCode: ${(error as BusinessError).code},  Message: ${(error as BusinessError).message}`);
          }
        })
        .javaScriptAccess(true)
        .domStorageAccess(true)
        .fileAccess(true)
        .geolocationAccess(false);
    };
  }

  private async interceptRequest(request: webview.WebSchemeHandlerRequest,
    resourceHandler: webview.WebResourceHandler) {
    // 此处对不放行的所有请求进行拦截。拦截后,可使用rcp或则http发送请求,获取响应后,封装响应返回web。
    let body: string | undefined;
    if (request.getRequestMethod() == 'POST') {
      try {
        body = await this.getHttpBody(request);

        // 请求体不为空,可以对body做加密,如:
        // body = '###' + body + '###';
      } catch (error) {
        console.error(`[schemeHandler] ErrorCode: ${(error as BusinessError).code},  Message: ${(error as BusinessError).message}`);
      }
    }

    let header = request.getHeader();
    let headerMap = new Map<string, string>();
    for (let i = 0; i < header.length; i++) {
      console.info('[schemeHandler] onRequestStart header:' + header[i].headerKey + ' ' + header[i].headerValue);
      headerMap.set(header[i].headerKey, header[i].headerValue);
    }

    let headers: rcp.RequestHeaders = JSON.parse(JSON.stringify(headerMap)) as rcp.RequestHeaders;

    // 使用rcp发送请求
    const session = rcp.createSession();
    let req = new rcp.Request(request.getRequestUrl(), request.getRequestMethod(), headers, body);
    session.fetch(req).then((response) => {
      console.info(`Succeeded in getting the response ${response}`);

      // 构造响应体返回
      let webResponse = new webview.WebSchemeHandlerResponse();
      try {
        webResponse.setNetErrorCode(WebNetErrorList.NET_OK);
        webResponse.setStatus(200);
        webResponse.setStatusText('OK');
        webResponse.setMimeType('text/html');
        webResponse.setEncoding('utf-8');
        webResponse.setHeaderByName('header1', 'value1', false);
      } catch (error) {
        console.error(`[schemeHandler] ErrorCode: ${(error as BusinessError).code},  Message: ${(error as BusinessError).message}`);
      }

      // 调用didFinish/didFail前需要优先调用didReceiveResponse将构造的响应头传递给被拦截的请求。
      try {
        console.info('[schemeHandler] length 1');
        resourceHandler.didReceiveResponse(webResponse);
        // 这里可以对响应体进行解密
        resourceHandler.didReceiveResponseBody(response.body);
        resourceHandler.didFinish();
      } catch (error) {
        console.error(`[schemeHandler] ErrorCode: ${(error as BusinessError).code},  Message: ${(error as BusinessError).message}`);
      }

    }).catch((err: BusinessError) => {
      console.error(`err: err code is ${err.code}, err message is ${JSON.stringify(err)}`);
    });
  }

  private async getHttpBody(request: webview.WebSchemeHandlerRequest): Promise<string | undefined> {
    let stream = request.getHttpBodyStream();
    if (stream) {
      await stream.initialize();
      if (!stream) {
        console.error('[schemeHandler] HttpBodyStream initialize failed.');
        return;
      }
      let size = stream.getSize();
      console.info(`[schemeHandler] HttpBodyStream size is ${size}`);
      let result = await stream.read(size);
      console.info(`[schemeHandler] HttpBodyStream buffer length is ${result.byteLength}`);
      // 从buffer中转换请求体内容
      let decoder = util.TextDecoder.create('utf-8');
      let requestBodyStr = decoder.decodeToString(new Uint8Array(result));
      console.info(`[schemeHandler] HttpBodyStream requestBody is ${requestBodyStr}`);
      return requestBodyStr;
    } else {
      console.info('[schemeHandler] onRequestStart has no http body stream');
      return undefined;
    }
  }
}

如果不能满足您的要求,麻烦您提供下以下信息:

请问您是在arkweb实现透明加解密,让加载的网页无感的业务场景中使用该能力,交互流程是怎样的,比如:服务器接收密码的时候怎么处理,在哪一个环节遇到了问题?您是希望基于arkweb提供能力,还是希望联合其他模块来实现呢?方便说明能力不满足可能带来的影响:什么时间用到?是否高频?有无三方库可以做到?若提供该能力,是否会造成大工作量返工?请您注意提供的内容不要包含您或第三方的非公开信息,如给您带来不便,敬请谅解。

更多关于HarmonyOS鸿蒙Next中arkweb是否可以实现透明加解密,让加载的网页无感?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


不是很了解 插眼学习一下

web组件貌似没有这么高级的功能。。。。

用https协议

什么意思?请指点

找HarmonyOS工作还需要会Flutter技术的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:https://www.bilibili.com/video/BV1S4411E7LY/?p=17,

网站服务器用https协议部署网站

https/ssl/tls不满足项目要求,需要在这个基础上再加一层国密,或者要自己实现https……

WebSchemeHandler 自定义协议 这种方式通过自定义协议(如app://)完全接管请求,更灵活,但需要修改网页代码中的请求URL。

图片

网页本身没有鸿蒙arkweb也要求能正常运行,不能用自定义的。

然后http协议拦截好像不太行……

ArkWeb目前不支持透明加解密功能。加载的网页内容无法在用户无感知的情况下自动进行加解密处理。

根据你描述的需求,在HarmonyOS Next的ArkWeb中实现网页请求和响应的透明加解密是可行的,但需要采用特定的方法。

核心思路是:既然无法直接拦截并修改原始响应,我们可以通过拦截请求并返回自定义响应的方式来“模拟”整个请求-响应流程,在这个过程中完成加解密。

实现方案分析

文档明确指出,onInterceptRequest()WebSchemeHandler 无法拦截响应,但可以自定义响应。这正是关键所在。我们可以:

  1. 拦截所有需要处理的网络请求(如 /api/*)。
  2. 在Native端(ArkTS)接管这个请求,而不是让WebView直接发往服务器。
  3. 在Native端:
    • 对请求体进行加密
    • 使用ArkTS的网络能力(如http模块)重新发起加密后的请求到真实后端。
    • 获取后端返回的加密响应。
    • 对响应体进行解密
  4. 将解密后的数据,构造为一个自定义的WebResourceResponse,直接返回给WebView中的页面。

这样,对于网页中的JavaScript来说,它发起了一个普通请求,并收到了一个“明文”响应,整个过程是无感的。加解密逻辑完全在ArkTS侧完成。

简单实现示例(概念代码)

以下是一个基于 onInterceptRequest() 的简化示例,演示如何为 /api/login 路径实现“添加/去除###”的加解密逻辑:

// 在ArkTS的Web组件中使用
import web_webview from '@ohos.web.webview';
import http from '@ohos.net.http';
import { BusinessError } from '@ohos.base';

@Entry
@Component
struct Index {
  controller: web_webview.WebviewController = new web_webview.WebviewController();

  aboutToAppear() {
    // 设置请求拦截回调
    this.controller.onInterceptRequest((request: web_webview.WebResourceRequest) => {
      let url = request.requestUrl.toString();
      
      // 1. 判断是否需要拦截(例如所有/api/开头的请求)
      if (url.indexOf('/api/') !== -1) {
        // 2. 接管请求,返回一个Promise,我们将提供自定义响应
        return new Promise((resolve, reject) => {
          this.handleInterceptedRequest(request).then((customResponse: web_webview.WebResourceResponse) => {
            // 5. 将处理好的自定义响应返回给WebView
            resolve(customResponse);
          }).catch((err: BusinessError) => {
            // 出错时,可以返回一个错误响应或拒绝Promise让请求继续
            reject(err);
          });
        });
      }
      // 对于不需要拦截的请求,返回null,WebView会正常处理
      return null;
    });
  }

  // 3. 处理被拦截的请求
  async handleInterceptedRequest(request: web_webview.WebResourceRequest): Promise<web_webview.WebResourceResponse> {
    // 假设是POST请求,获取请求体(这里需要根据实际请求方法处理)
    let requestBody: string = ''; // 实际应从request中获取请求体,可能需要异步读取
    // 注意:获取原始请求体可能涉及流处理,此处为简化示例。

    // 模拟“加密”:在body首尾添加###
    let encryptedBody = `###${requestBody}###`;

    // 4. 使用ArkTS的http模块,向真实后端发送加密后的请求
    let httpRequest = http.createHttp();
    let options: http.HttpRequestOptions = {
      method: http.RequestMethod.POST, // 保持原方法
      header: request.requestHeaders, // 传递原始headers
      extraData: encryptedBody, // 发送加密后的body
      readTimeout: 60000,
      connectTimeout: 60000
    };

    try {
      let response = await httpRequest.request(request.requestUrl.toString(), options);
      
      // 获取后端返回的加密响应体
      let encryptedResponseBody = await response.result.toString();
      
      // 模拟“解密”:去掉首尾的###
      let decryptedResponseBody = encryptedResponseBody.replace(/^###|###$/g, '');

      // 构造自定义响应返回给WebView
      let customResponse: web_webview.WebResourceResponse = {
        responseData: decryptedResponseBody, // 解密后的数据
        responseHeaders: response.header, // 使用后端返回的headers
        responseMimeType: 'application/json', // 根据实际情况设置
        responseEncoding: 'utf-8',
        statusCode: response.responseCode,
        reasonPhrase: 'OK'
      };
      return customResponse;
    } catch (error) {
      // 错误处理
      console.error(`Request failed: ${JSON.stringify(error)}`);
      throw error;
    } finally {
      httpRequest.destroy();
    }
  }

  build() {
    Column() {
      Web({ src: 'www.example.com/yourpage.html', controller: this.controller })
        .width('100%')
        .height('100%')
    }
  }
}

关键点与注意事项

  1. 请求体获取:上述示例中 requestBody 的获取是简化的。实际应用中,如果请求体是表单数据或JSON,你需要从 WebResourceRequest 对象中正确提取,这可能涉及处理请求流。
  2. 性能考量:拦截所有API请求并在Native端中转,会增加一定的延迟和Native端的处理负担。需评估对性能的影响。
  3. 错误处理:必须做好全面的错误处理(网络错误、加解密失败、解析失败等),并向WebView返回适当的错误响应(如4xx, 5xx状态码),否则网页逻辑可能异常。
  4. Header处理:示例中简单传递了原始请求头。实际可能需要过滤或修改某些Header(如Content-Length需要重新计算)。
  5. WebSchemeHandler:对于更复杂或需要更精细控制的场景(如自定义协议myapp://api/),可以使用WebSchemeHandler。其原理类似,注册自定义协议处理器,完全由ArkTS侧接管该协议下所有资源的请求与响应,实现方式更为彻底。

总结

你的需求可以通过 “拦截请求 -> Native端代理请求并加解密 -> 返回自定义响应” 的模式在HarmonyOS Next的ArkWeb中实现。虽然不能直接修改原始响应流,但通过返回一个全新的、处理过的响应,可以达到让网页无感加解密的最终效果。示例代码提供了使用 onInterceptRequest() 的基本框架,你需要根据实际的请求体格式、加解密算法和错误处理逻辑进行完善。

回到顶部