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

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

背景

阿里云HTTPDNS是避免dns劫持的一种有效手段,在许多特殊场景如[HTTPS/SNI]、[okhttp]等都有最佳实践,但在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被劫持的状况。

目录
相关文章
|
2月前
|
IDE Android开发 iOS开发
深入解析Android与iOS的系统架构及开发环境差异
本文旨在探讨Android和iOS两大主流移动操作系统在系统架构、开发环境和用户体验方面的显著差异。通过对比分析,我们将揭示这两种系统在设计理念、技术实现以及市场策略上的不同路径,帮助开发者更好地理解其特点,从而做出更合适的开发决策。
164 2
|
3月前
|
并行计算 数据挖掘 大数据
[go 面试] 并行与并发的区别及应用场景解析
[go 面试] 并行与并发的区别及应用场景解析
|
18天前
|
JavaScript API 开发工具
<大厂实战场景> ~ Flutter&鸿蒙next 解析后端返回的 HTML 数据详解
本文介绍了如何在 Flutter 中解析后端返回的 HTML 数据。首先解释了 HTML 解析的概念,然后详细介绍了使用 `http` 和 `html` 库的步骤,包括添加依赖、获取 HTML 数据、解析 HTML 内容和在 Flutter UI 中显示解析结果。通过具体的代码示例,展示了如何从 URL 获取 HTML 并提取特定信息,如链接列表。希望本文能帮助你在 Flutter 应用中更好地处理 HTML 数据。
100 1
|
23天前
|
程序员 开发工具 Android开发
Android|WebView 禁止长按,限制非白名单域名的跳转层级
如何限制 WebView 仅域名白名单网址能随意跳转,并禁用长按选择文字。
28 2
|
1月前
|
存储
让星星⭐月亮告诉你,HashMap的put方法源码解析及其中两种会触发扩容的场景(足够详尽,有问题欢迎指正~)
`HashMap`的`put`方法通过调用`putVal`实现,主要涉及两个场景下的扩容操作:1. 初始化时,链表数组的初始容量设为16,阈值设为12;2. 当存储的元素个数超过阈值时,链表数组的容量和阈值均翻倍。`putVal`方法处理键值对的插入,包括链表和红黑树的转换,确保高效的数据存取。
53 5
|
1月前
|
测试技术 数据库 Android开发
深入解析Android架构组件——Jetpack的使用与实践
本文旨在探讨谷歌推出的Android架构组件——Jetpack,在现代Android开发中的应用。Jetpack作为一系列库和工具的集合,旨在帮助开发者更轻松地编写出健壮、可维护且性能优异的应用。通过详细解析各个组件如Lifecycle、ViewModel、LiveData等,我们将了解其原理和使用场景,并结合实例展示如何在实际项目中应用这些组件,提升开发效率和应用质量。
44 6
|
2月前
|
存储 开发框架 数据可视化
深入解析Android应用开发中的四大核心组件
本文将探讨Android开发中的四大核心组件——Activity、Service、BroadcastReceiver和ContentProvider。我们将深入了解每个组件的定义、作用、使用方法及它们之间的交互方式,以帮助开发者更好地理解和应用这些组件,提升Android应用开发的能力和效率。
179 5
|
2月前
|
缓存 Android开发 开发者
Android RecycleView 深度解析与面试题梳理
本文详细介绍了Android开发中高效且功能强大的`RecyclerView`,包括其架构概览、工作流程及滑动优化机制,并解析了常见的面试题。通过理解`RecyclerView`的核心组件及其优化技巧,帮助开发者提升应用性能并应对技术面试。
89 8
|
2月前
|
存储 缓存 Android开发
Android RecyclerView 缓存机制深度解析与面试题
本文首发于公众号“AntDream”,详细解析了 `RecyclerView` 的缓存机制,包括多级缓存的原理与流程,并提供了常见面试题及答案。通过本文,你将深入了解 `RecyclerView` 的高性能秘诀,提升列表和网格的开发技能。
67 8
|
1月前
|
安全 网络安全 Android开发
深度解析:利用Universal Links与Android App Links实现无缝网页至应用跳转的安全考量
【10月更文挑战第2天】在移动互联网时代,用户经常需要从网页无缝跳转到移动应用中。这种跳转不仅需要提供流畅的用户体验,还要确保安全性。本文将深入探讨如何利用Universal Links(仅限于iOS)和Android App Links技术实现这一目标,并分析其安全性。
224 0

相关产品

  • 云解析DNS
  • 推荐镜像

    更多