Android Webview场景下防止dns劫持的探索

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 阿里云HTTPDNS是避免dns劫持的一种有效手段,在许多特殊场景都有最佳实践,拦截方案是目前已知的一种在webview上应用httpdns的可行方案,本文从拦截方案的基本原理出发,尝试分析该方案背后存在的局限,并给出一些可行性上的建议。

背景

阿里云HTTPDNS是避免dns劫持的一种有效手段,在许多特殊场景如HTTPS/SNIokhttp等都有最佳实践,但在webview场景下却一直没完美的解决方案。

拦截方案是目前已知的一种在webview上应用httpdns的可行方案,本文从拦截方案的基本原理出发,尝试分析该方案背后存在的局限,并给出一些可行性上的建议。

基本原理

拦截方案是指通过对webview进行配置WebViewClient来做到对网络请求的拦截:

void setWebViewClient (WebViewClient client);

拦截方案的的调用流程如下图所示:

webview_research

Webview相关的网络请求由系统的chromium网络库发起,Webview调用loadUrl方法时,chromium网络库会构造URLRequest实例,经过c层到java层,最终请求参数会回调给上层WebViewClientshouldInterceptRequest方法,而我们的目标是在shouldInterceptRequest方法中通过HTTPDNS进行URL中域名到ip的替换,并且构造和返回合法的WebResourceResponse,让webview在避免dns劫持的同时,也能正常地进行展示。

局限

首先,当Android API < 21时,WebViewClient提供的拦截API如下:

public WebResourceResponse shouldInterceptRequest(WebView view, String url);

此时shouldInterceptRequest只能拿到URL,而请求方法、头部等这些信息是拿不到的,强行拦截会造成请求信息的丢失,由此可知局限1:

局限1:Android API < 21只能拦截网络请求的URL,请求方法、请求头等无法拦截;

其次,对于Android API >= 21的情况,其拦截API为:

public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request);

第2个参数变成了WebResourceRequest,其结构如下:

public interface WebResourceRequest {
    Uri getUrl();
    boolean isForMainFrame();
    boolean isRedirect();
    boolean hasGesture();
    String getMethod();
    Map<String, String> getRequestHeaders();
}

相比之下WebResourceRequest能给的多了MethodHeader以及是否重定向,但是没有Body,也无法预知该请求是否可能携带body,对于带body的符合协议但非标的Get请求一样无法拦截,因此局限2:

局限2:Android API >= 21无法拦截body,拦截方案只能正常处理不带body的请求;

接下来,我们看下要构造的WebResourceResponse,这个类需要我们提供MIMEencodingInputStream。其中,MIMEencoding可以通过解析响应头的content-type来获得:

content-type:text/html;charset=UTF-8

但是,对于js/css/image等类型的资源请求是没有charset的,强行拦截会因为编码问题造成js/css/image的加载/显示异常,因此局限3:

局限3MIMEencoding可通过解析响应头的content-type来获得,但有时会拿不到;

可行性

看完了上面的各种局限,是不是拦截方案就完全行不可行呢?其实也不尽然,我们来看下面一段代码:

        @SuppressLint("NewApi")
        @Override
        public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {

            final String scheme = request.getUrl().getScheme().trim();
            final String url = request.getUrl().toString();
            final Map<String, String> headerFields = request.getRequestHeaders();

            // #1 只拦截get方法
            if (request.getMethod().equalsIgnoreCase("get") && (scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https"))) {
                try {
                    final URL oldUrl = new URL(url);
                    HttpURLConnection conn;

                    // #2 通过httpdns替换ip
                    final String ip = mService.getIpByHostAsync(oldUrl.getHost());
                    if (TextUtils.isEmpty(ip)) {
                        final String host = oldUrl.getHost();
                        final String newUrl = url.replaceFirst(host, ip);

                        // #3 设置HTTP请求头Host域
                        conn = (HttpURLConnection) new URL(newUrl).openConnection();
                        conn.setRequestProperty("Host", host);

                        // #4 设置HTTP请求header
                        for (String header : headerFields.keySet()) {
                            conn.setRequestProperty(header, headerFields.get(header));
                        }

                        // #5 处理https场景
                        if (conn instanceof HttpsURLConnection) {
                            ((HttpsURLConnection) conn).setHostnameVerifier(new HostnameVerifier() {
                                @Override
                                public boolean verify(String hostname, SSLSession session) {
                                    return HttpsURLConnection.getDefaultHostnameVerifier().verify(host, session);
                                }
                            });
                        }

                        // #6 拿到MINE和encoding
                        final String contentType = conn.getContentType();
                        final String mine = getMine(contentType);
                        final String encoding = getEncoding(contentType);

                        // #7 MINE和encoding拿不到的情况下,不拦截
                        if (TextUtils.isEmpty(mine) || TextUtils.isEmpty(encoding)) {
                            return super.shouldInterceptRequest(view, request);
                        }

                        return new WebResourceResponse(mine, encoding, conn.getInputStream());
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            return super.shouldInterceptRequest(view, request);
        }

值得注意的是,如果webview中承载的内容是app为自身业务打造的,可控的,那就完全可以通过开发规范来绕开部分局限,也能在一定程度上通过httpdns来改善webview上的dns被劫持的状况。

目录
相关文章
|
23天前
|
并行计算 数据挖掘 大数据
[go 面试] 并行与并发的区别及应用场景解析
[go 面试] 并行与并发的区别及应用场景解析
|
2月前
|
Java 调度 Android开发
深入解析Android应用开发中的响应式编程与RxJava应用
在现代Android应用开发中,响应式编程及其核心框架RxJava正逐渐成为开发者的首选。本文将深入探讨响应式编程的基本概念、RxJava的核心特性以及如何在Android应用中利用RxJava提升代码的可读性和性能。 【7月更文挑战第7天】
|
30天前
|
机器学习/深度学习 存储 人工智能
提升深度学习性能的利器—全面解析PAI-TorchAcc的优化技术与应用场景
在当今深度学习的快速发展中,模型训练和推理的效率变得尤为重要。为了应对计算需求不断增长的挑战,AI加速引擎应运而生。其中,PAI-TorchAcc作为一个新兴的加速引擎,旨在提升PyTorch框架下的计算性能。本文将详细介绍PAI-TorchAcc的基本概念、主要特性,并通过代码实例展示其性能优势。
17950 159
|
2月前
|
设计模式 Java
Java中的设计模式及其应用场景解析
Java中的设计模式及其应用场景解析
|
7天前
|
运维 监控 数据可视化
Elasticsearch全观测技术解析问题之面对客户不同的场景化如何解决
Elasticsearch全观测技术解析问题之面对客户不同的场景化如何解决
|
9天前
|
存储 数据挖掘 大数据
深度解析Hologres计算资源配置:如何根据业务场景选择合适的计算类型?
【8月更文挑战第22天】Hologres是一款由阿里云提供的分布式分析型数据库,支持高效的大数据处理与分析。本文通过电商优化商品推荐策略的案例,介绍了Hologres中的计算组型与通用型配置。计算组型提供弹性扩展资源,适合大规模数据及高并发查询;通用型则适用于多数数据分析场景,具备良好计算性能。通过实例创建、数据加载、计算任务建立及结果查询的步骤展示,读者可理解两种配置的差异并根据业务需求灵活选择。
23 2
|
13天前
|
JSON Java Android开发
Android 开发者必备秘籍:轻松攻克 JSON 格式数据解析难题,让你的应用更出色!
【8月更文挑战第18天】在Android开发中,解析JSON数据至关重要。JSON以其简洁和易读成为首选的数据交换格式。开发者可通过多种途径解析JSON,如使用内置的`JSONObject`和`JSONArray`类直接操作数据,或借助Google提供的Gson库将JSON自动映射为Java对象。无论哪种方法,正确解析JSON都是实现高效应用的关键,能帮助开发者处理网络请求返回的数据,并将其展示给用户,从而提升应用的功能性和用户体验。
25 1
|
22天前
|
存储 数据库 C++
"深入剖析Python元组(tuple):与列表的对比、特性解析及高效应用场景展示"
【8月更文挑战第9天】Python元组与列表虽均用于存储元素集合,但有本质差异。元组不可变,创建后无法修改,适合保护数据不被意外更改的场景,如作字典键或传递固定值。列表则可变,支持动态增删改,适用于需频繁调整的数据集。元组因不可变性而在性能上有优势,可用于快速查找。两者各有千秋,根据具体需求选择使用。例如,元组可用于表示坐标点或日期,而列表更适合管理用户列表或库存。
24 1
|
2月前
|
Oracle NoSQL 固态存储
阿里云服务器ESSD Entry云盘与ESSD云盘选择指南:性能与场景解析
在我们选择阿里云服务器的时候,有部分云服务器同时支持ESSD Entry云盘和ESSD云盘,选择不同的云盘,价格也有所差异,有的用户还不清楚他们之间的区别,因此不知道选择哪种更好更能满足自己场景的需求,本文为大家介绍一下阿里云服务器ESSD Entry云盘和ESSD云盘的区别及选择参考。
阿里云服务器ESSD Entry云盘与ESSD云盘选择指南:性能与场景解析
|
8天前
|
测试技术 API Android开发
Android经典实战之简化 Android 相机开发:CameraX 库的全面解析
CameraX是Android Jetpack的一个组件,旨在简化相机应用开发,提供了易于使用的API并支持从Android 5.0(API级别21)起的设备。其主要特性包括广泛的设备兼容性、简洁的API、生命周期感知、简化实现及方便的集成与测试。通过简单的几个步骤即可实现如拍照、视频录制等功能。此外,还提供了最佳实践指导以确保应用的稳定性和性能。
23 0

相关产品

  • 云解析DNS
  • 推荐镜像

    更多
    下一篇
    云函数