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

本文涉及的产品
云解析DNS,个人版 1个月
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 阿里云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天前
|
弹性计算 缓存 应用服务中间件
阿里云服务器2核2G99元和2核4G199元实例规格性能及适用场景解析
2024年阿里云推出了两款云服务器,2核2G3M带宽40G ESSD Entry盘价格只要99元1年,2核4G5M带宽80G ESSD Entry盘价格只要199元1年,这两款云服务器的活动截止日期为2026年3月31日,活动期间新购、续费同价。那么这两款云服务器怎么样呢?可以用来做什么?本文将对这两款云服务器进行深度解析,包括配置介绍、实例规格、使用场景以及购买建议,以供选择参考。
阿里云服务器2核2G99元和2核4G199元实例规格性能及适用场景解析
|
1月前
|
XML 存储 JSON
51. 【Android教程】JSON 数据解析
51. 【Android教程】JSON 数据解析
31 2
|
2月前
|
设计模式 前端开发 Android开发
Android应用开发中的MVP架构模式解析
【5月更文挑战第25天】本文深入探讨了在Android应用开发中广泛采用的一种设计模式——Model-View-Presenter (MVP)。文章首先概述了MVP架构的基本概念和组件,接着分析了它与传统MVC模式的区别,并详细阐述了如何在实际开发中实现MVP架构。最后,通过一个具体案例,展示了MVP架构如何提高代码的可维护性和可测试性,以及它给开发者带来的其他潜在好处。
|
2月前
|
域名解析 监控 网络协议
【域名解析DNS专栏】DNS域名劫持与防范策略:保护你的域名安全
【5月更文挑战第26天】DNS域名劫持是网络攻击手法,攻击者篡改DNS记录,将用户导向恶意网站,威胁隐私泄露、数据窃取及品牌信誉。防范策略包括使用DNSSEC加密验证响应,选择安全的DNS服务提供商,定期检查DNS记录,以及教育员工和用户识别网络威胁。通过这些措施,可以增强域名安全,抵御DNS劫持攻击。
|
11天前
|
存储 缓存 监控
深入JVM:解析OOM的三大场景,原因及实战解决方案
深入JVM:解析OOM的三大场景,原因及实战解决方案
|
20天前
|
安全 JavaScript 前端开发
kotlin开发安卓app,JetPack Compose框架,给webview新增一个按钮,点击刷新网页
在Kotlin中开发Android应用,使用Jetpack Compose框架时,可以通过添加一个按钮到TopAppBar来实现WebView页面的刷新功能。按钮位于右上角,点击后调用`webViewState?.reload()`来刷新网页内容。以下是代码摘要:
|
21天前
|
JavaScript 前端开发 Android开发
kotlin安卓在Jetpack Compose 框架下使用webview , 网页中的JavaScript代码如何与native交互
在Jetpack Compose中使用Kotlin创建Webview组件,设置JavaScript交互:`@Composable`函数`ComposableWebView`加载网页并启用JavaScript。通过`addJavascriptInterface`添加`WebAppInterface`类,允许JavaScript调用Android方法如播放音频。当页面加载完成时,执行`onWebViewReady`回调。
|
2月前
|
弹性计算 数据挖掘 应用服务中间件
阿里云服务器通用算力型U1实例解析,实例性能、适用场景及常见问题参考
在阿里云服务器的所有实例规格中,通用算力型u1实例主打的是高性价比,通用算力型U1实例云服务器自推出以来,就受到了广大用户的关注,也是目前阿里云的活动中比较热门的云服务器实例,这个实例规格的性能要好于经济型e等共享型实例,价格又比计算型c7、通用型g7等其他企业级实例要低一些。本文将深入解析通用算力型U1实例的特点、适用场景以及价格优势,帮助用户更好地了解该云服务器实例。
阿里云服务器通用算力型U1实例解析,实例性能、适用场景及常见问题参考
|
5天前
|
Web App开发 JavaScript 前端开发
Android端使用WebView注入一段js代码实现js调用android
Android端使用WebView注入一段js代码实现js调用android
15 0
|
11天前
|
消息中间件 存储 运维
RocketMQ与Kafka深度对比:特性与适用场景解析
RocketMQ与Kafka深度对比:特性与适用场景解析

相关产品

  • 云解析DNS
  • 推荐镜像

    更多