iOSBlog icon indicating copy to clipboard operation
iOSBlog copied to clipboard

iOS 防 DNS 污染方案调研(三)--- WebView 业务场景

Open ChenYilong opened this issue 8 years ago • 10 comments

iOS防DNS污染方案调研---WebView业务场景

对应的GitHub仓库镜像地址在这里 ,欢迎提PR进行修改。

概述

本文主要介绍,防 DNS 污染方案在 WebView 场景下所遇到的一些问题,及解决方案,也会涉及比如:“HTTPS+SNI” 等场景。

面临的问题

/one more thing/

WKWebView 无法使用 NSURLProtocol 拦截请求

方案如下:

  1. 换用 UIWebView
  2. 使用私有API进行注册拦截

换用 UIWebView 方案不做赘述,说明下使用私有API进行注册拦截的方法:

//注册自己的protocol
   [NSURLProtocol registerClass:[CustomProtocol class]];

   //创建WKWebview
   WKWebViewConfiguration * config = [[WKWebViewConfiguration alloc] init];
   WKWebView * wkWebView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height) configuration:config];
   [wkWebView loadRequest:webViewReq];
   [self.view addSubview:wkWebView];

   //注册scheme
   Class cls = NSClassFromString(@"WKBrowsingContextController");
   SEL sel = NSSelectorFromString(@"registerSchemeForCustomProtocol:");
   if ([cls respondsToSelector:sel]) {
       // 通过http和https的请求,同理可通过其他的Scheme 但是要满足ULR Loading System
       [cls performSelector:sel withObject:@"http"];
       [cls performSelector:sel withObject:@"https"];
   }

使用私有 API 的另一风险是兼容性问题,比如上面的 browsingContextController 就只能在 iOS 8.4 以后才能用,反注册 scheme 的方法 unregisterSchemeForCustomProtocol: 也是在 iOS 8.4 以后才被添加进来的,要支持 iOS 8.0 ~ 8.3 机型的话,只能通过动态生成字符串的方式拿到 WKBrowsingContextController,而且还不能反注册,不过这些问题都不大。至于向后兼容,这个也不用太担心,因为 iOS 发布新版本之前都会有开发者预览版的,那个时候再测一下也不迟。对于本文的例子来说,如果将来哪个 iOS 版本移除了这个 API,那很可能是因为官方提供了完整的解决方案,到那时候自然也不需要本文介绍的方法了。

注意避免执行太晚,如果在 - (void)viewDidLoad 中注册,可能会因为注册太晚,引发问题。建议在 +load 方法中执行。

然后同样会遇到 《iOS SNI 场景下的防 DNS 污染方案调研》 里提到的各种 NSURLProtocol 相关的问题,可以参照里面的方法解决。

Cookie相关问题

单独成篇: 《防 DNS 污染方案调研---iOS HTTPS(含SNI) 业务场景(四)-- Cookie 场景》

302重定向问题

上面提到的 Cookie 方案无法解决302请求的 Cookie 问题,比如,第一个请求是 http://www.a.com ,我们通过在 request header 里带上 Cookie 解决该请求的 Cookie 问题,接着页面302跳转到 http://www.b.com ,这个时候 http://www.b.com 这个请求就可能因为没有携带 cookie 而无法访问。当然,由于每一次页面跳转前都会调用回调函数:

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;

可以在该回调函数里拦截302请求,copy request,在 request header 中带上 cookie 并重新 loadRequest。不过这种方法依然解决不了页面 iframe 跨域请求的 Cookie 问题,毕竟-[WKWebView loadRequest:]只适合加载 mainFrame 请求。

参考链接

相关的库:

相关的文章:

可以参考的Demo:

走过的弯路

误以为 iOS11 新 API 可以原生拦截 WKWebView 的 HTTP/HTTPS 网络请求

参考:Deal With WKWebView DNS pollution problem in iOS11

ChenYilong avatar Jul 20 '17 08:07 ChenYilong

//TODO: 下一步需要测试下,拦截所有网络请求这个方案与原生方式的性能对比,对比方式见SNI篇。

测试时需要加载非常复杂的页面,比如一个页面上百次的网络请求,比如:测试1测试2 等。

测试系统:iOS 8 - iOS 11,对比Safari、系统WebView原生加载、拦截所有网络请求这个方案。

ChenYilong avatar Aug 09 '17 06:08 ChenYilong

你好,可以请教个问题吗?WKWebView在NSURLProtocol里面有post请求body丢失的问题,有解决方案吗?

JeroldLiu777 avatar Aug 22 '17 09:08 JeroldLiu777

@lucifer717 上面提到了一种方法,在 https://github.com/ChenYilong/iOSBlog/issues/12 这里也进行了说明,你看下能否解决你的问题。

ChenYilong avatar Aug 22 '17 09:08 ChenYilong

/**使用 NSURLProtocol 拦截 NSURLSession 请求丢失 body

方案如下:

换用 NSURLConnection 将 body 放进 Header 中 使用 HTTPBodyStream 获取 body,并赋值到 body 中 **/

请问这里的把 body 放进 header 里能说的更清楚吗?在哪里放,h5放还是还是native放,如果native放,怎么保证后续的请求都能放,如果放在h5,怎么保证第三方加入这个header,这也是问题。

所以请问楼主有在实践中使用过你的方案吗?

karosLi avatar Aug 28 '17 09:08 karosLi

使用测试链接进行测试,一个页面有80余个网络请求,测试对比:

其中的网络请求:多数为 HTTPS 的图片、JS、CSS资源文件。

加载完成上面的测试链接,平均耗时:

防DNS污染方案 原生方案
16+1.9+1.6+2.4+2.7+1.5+3.9+2.2+1.2+.3=33.7/10=3.37 7+1.4+4.0+2.3+1.3+1.8+1.3+1.9+1.9+2=24.9/10=2.49

均采用步骤:

第一次安装app,第一次打开页面的方式,之后的测试是采用密集点击进页面加载网页。

可以看到第一次网络请求差距较大,之后的网络请求延迟与原生请求接近。

性能消耗集中在证书验证部分。

ChenYilong avatar Aug 28 '17 09:08 ChenYilong

上文有误:

WKWebView不能使用下面的方法来恢复body内容:

下面这个方法仅仅适用于HTTPBodyStream有内容的场景,一般的UIWebView与NSURLSession请求都可以,但是参考:

由于 WKWebView 在独立进程里执行网络请求。一旦注册 http(s) scheme 后,网络请求将从 Network Process 发送到 App Process,这样 NSURLProtocol 才能拦截网络请求。在 webkit2 的设计里使用 MessageQueue 进行进程之间的通信,Network Process 会将请求 encode 成一个 Message,然后通过 IPC 发送给 App Process。出于性能的原因,encode 的时候 HTTPBody 和 HTTPBodyStream 这两个字段被丢弃掉了(参考苹果源码: https://github.com/WebKit/webkit/blob/fe39539b83d28751e86077b173abd5b7872ce3f9/Source/WebKit2/Shared/mac/WebCoreArgumentCodersMac.mm#L61-L88 及 bug report: <WKWebView does not fully support custom NSURLProtocol> )。

//处理POST请求相关POST  用HTTPBodyStream来处理BODY体
- (void)handlePostRequestBody {
  if ([self.HTTPMethod isEqualToString:@"POST"]) {
      if (!self.HTTPBody) {
          uint8_t d[1024] = {0};
          NSInputStream *stream = self.HTTPBodyStream;
          NSMutableData *data = [[NSMutableData alloc] init];
          [stream open];
          while ([stream hasBytesAvailable]) {
              NSInteger len = [stream read:d maxLength:1024];
              if (len > 0 && stream.streamError == nil) {
                  [data appendBytes:(void *)d length:len];
              }
          }
          self.HTTPBody = [data copy];
          [stream close];
      }
  }
}

ChenYilong avatar Sep 26 '17 02:09 ChenYilong

wkwebview http请求拦截是无解的了

jiehu5114 avatar Mar 27 '18 11:03 jiehu5114

@ChenYilong WKWebView 支持302 跨域吗,或者说怎么设置

kilolumen avatar Nov 02 '18 07:11 kilolumen

ChenYilong avatar Jul 06 '19 09:07 ChenYilong

我把 相关的方案汇总了下, 做了个视频, 有兴趣的可以看看 《WKWebView所有适配方案横向测评》 https://youtu.be/9egNGGGKET8 via 放在YouTube 其中ajax hook 部分, 可以解决DNS WebView场景这个问题;

ChenYilong avatar Nov 26 '20 07:11 ChenYilong