WebView域名劫持问题以及自签名证书验证方式

一,背景介绍

WebView尽管有着各种各样的问题,但是至今为止很多前端页面加载到客户端还是要依靠WebView去加载,WebView加载的链接可以是不安全不需要经过证书认证的http,ws协议的域名,当然为了数据的安全性,大部分网页还是会采取https和wss协议的域名,在使用https或wss协议的域名过程中,就会出现域名劫持问题,导致用户无法正常访问,客户端会在Webview的回调函数中收到sslError,一般来说这时候我们会向服务器验证证书的合法性,也就是自签名证书的验证,验证通过可以忽略证书错误,验证不通过弹窗提示用户证书错误。举个例子,新疆地区有一些域名就会被拦截,这些域名可能在其他地区是正常的,导致用户无法正常查看网页。

二,https,wss与ssl的关系

1.https和wss都是基于ssl的加密安全协议
2.SSL(Secure Socket Layer,安全套接层) 简单来说是一种加密技术, 通过它, 我们可以在通信的双方上建立一个安全的通信链路, 因此数据交互的双方可以安全地通信, 而不需要担心数据被窃取。
3.总的来说,SSL就是基础,在SSL上运行Websocket就是WSS,在SSL上运行http就是https。

三,域名劫持的解决方案?

1.既然域名被劫持了,那我直接更换域名就ok了。更换的方式有2种,第一种是服务端全部更换,这样做是不好的,因为正常地区这个域名是未被劫持的,更好的方案是根据ip或者location下发被劫持域名和替换域名对应关系的配置进行域名替换。
2.如何替换呢?在loadUrl的时候就要根据拉取的配置是否需要替换域名,另外在WebView的shouldInterceptRequest回调函数中拦截页面的子请求,拉取服务器配置判断是否要替换域名,并且替换后的域名也要注入自签名证书。
3.收到sslError后再次向服务器发送一个请求,校验证书的合法性,校验通过则忽略证书错误,校验不通过则显示证书错误的对话框。
4.更简单直接的方案是前端同学去修改,直接更改所有请求方式通过前端与本地之前的桥走本地代发,这时候本地网络请求的代码肯定携带了自签名的证书,这时候网页内的请求也携带了自签名的证书。

四,校验自签名证书的原理和关键代码

1.需要拦截处理的位置。

//1.加载网页时需要判断是否替换url的域名
public void loadUrl(String url) {
    }

//2.WebViewClient中需要处理的函数
private WebViewClient webViewClient = new WebViewClient() {
      
      @Nullable
        @Override
        public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
            //交给webview自己处理时,需要替换拦截后的域名
            String interceptUrl = getInterceptUrl(url);
            return super.shouldInterceptRequest(view, interceptUrl);
        }

        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        @Nullable
        @Override
        public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
            //交给webview自己处理时,需要替换拦截后的域名
            String interceptUrl = getInterceptUrl(request.getUrl().toString());
            return super.shouldInterceptRequest(view, interceptUrl);
        }
        @Override
        public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
           //收到sslError后需要向服务器发一个请求再次校验证书的合法性,校验成功忽略证书错误,校验失败则取消
           //忽略证书错误
           handler.proceed();
           //取消
           handler.cancel();
        }
    };

2.使用Okhttp校验自签名证书的方式

	//获取OkhttpClient,getCustomSSL()是获取ssl证书的函数
	private static OkHttpClient getInterceptClient() {
        if (interceptClient == null) {
            try {
                OkHttpClient.Builder builder = getOkHttpClientBuilder();
                builder.sslSocketFactory(getCustomSSL()).followRedirects(false);
                interceptClient = builder.build();
            } catch (Throwable thr) {
                thr.printStackTrace();
            }
        }
        return interceptClient;
    }
    
	//获取SSLSocketFactory
 	private static SSLSocketFactory getCustomSSL() throws IOException {
        if (customSSL != null) {
            return customSSL;
        }
        String caFilename = "xxxCA.der";
        InputStream caInput = new BufferedInputStream(RefereeService.getInstance().getContext().getAssets().open(caFilename));
        InputStream[] cas = {caInput};
        customSSL = getSslSocketFactory(cas, null, null);
        return customSSL;
    }
	//根据证书密码等获取SSLSocketFactory
 	 public static SSLSocketFactory getSslSocketFactory(InputStream[] certificates, InputStream bksFile, String password) {
        try {
            KeyManager[] keyManagers = prepareKeyManager(bksFile, password);
            TrustManager[] trustManagers = prepareTrustManager(certificates);
            SSLContext sslContext = SSLContext.getInstance("TLS");
            TrustManager trustManager;
            if (trustManagers != null) {
                trustManager = new MyTrustManager(chooseTrustManager(trustManagers));
            } else {
                trustManager = new UnSafeTrustManager();
            }
            sslContext.init(keyManagers, new TrustManager[]{trustManager}, new SecureRandom());
            return sslContext.getSocketFactory();
        } catch (NoSuchAlgorithmException e) {
            throw new AssertionError(e);
        } catch (KeyManagementException e) {
            throw new AssertionError(e);
        } catch (KeyStoreException e) {
            throw new AssertionError(e);
        }
    }
    
	//准备KeyManager
	private static KeyManager[] prepareKeyManager(InputStream bksFile, String password) {
        try {
            if (bksFile == null || password == null) return null;
            KeyStore clientKeyStore = KeyStore.getInstance("BKS");
            clientKeyStore.load(bksFile, password.toCharArray());
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(clientKeyStore, password.toCharArray());
            return keyManagerFactory.getKeyManagers();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnrecoverableKeyException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
 	 //准备证书信任的Manager
  	private static TrustManager[] prepareTrustManager(InputStream... certificates) {
        if (certificates == null || certificates.length <= 0) return null;
        try {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null);
            int index = 0;
            for (InputStream certificate : certificates) {
                String certificateAlias = Integer.toString(index++);
                keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
                try {
                    if (certificate != null) certificate.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            TrustManagerFactory trustManagerFactory;
            trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            return trustManagerFactory.getTrustManagers();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
	private static X509TrustManager chooseTrustManager(TrustManager[] trustManagers) {
        for (TrustManager trustManager : trustManagers) {
            if (trustManager instanceof X509TrustManager) {
                return (X509TrustManager) trustManager;
            }
        }
        return null;
    }

    private static class UnSafeTrustManager implements X509TrustManager {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[]{};
        }
    }

    private static class MyTrustManager implements X509TrustManager {
        private X509TrustManager defaultTrustManager;
        private X509TrustManager localTrustManager;

        public MyTrustManager(X509TrustManager localTrustManager) throws NoSuchAlgorithmException, KeyStoreException {
            TrustManagerFactory var4 = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            var4.init((KeyStore) null);
            defaultTrustManager = chooseTrustManager(var4.getTrustManagers());
            this.localTrustManager = localTrustManager;
        }

        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            try {
                defaultTrustManager.checkServerTrusted(chain, authType);
            } catch (CertificateException ce) {
                localTrustManager.checkServerTrusted(chain, authType);
            }
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    }

3.校验自签名证书安全的方案其实本质上就是SSL加密与解密的过程,要想深刻理解还需要看一下SSL的实现方式。

五,总结

1.WebView是一个很大的方向,目前也是一个很成熟的控件。其实前端跨平台的方案还有weex,lua,flutter等等,万变不离其宗,这些跨平台方案目前能满足大多数的性能要求,如果对性能要求更高的话还是要原生去实现,出现问题时可以使用google浏览器提供的google://inspect工具去调试。
2.跨平台开发的另一个方向其实是NDK,也就是编译C,C++代码来达到数据的安全性和更高的性能要求,后续会写一篇文章详细介绍NDK编程相关的知识。

本人所学知识有限,如有描述不合理出请指正,如有问题也可以给本人发邮件,邮箱地址:[email protected]

你可能感兴趣的:(域名劫持,跨平台开发,android校验自签名证书,android校验自签名证书,WebView)