webview加载https链接(终极解决方案,包括https链接加载http图片显示失败)

前言:只要涉及到https,大家都会第一时间想到证书验证。当然,这是没问题的。如果有要求,这个证书验证是必须的。一般情况下,都是需要证书的(有证书毕竟安全些吗)

正常的解决办法:

重写类WebViewClient
第一种方法:

webView.setWebViewClient(new WebViewClient ()
                @Override
                public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
                    super.onReceivedSslError(view, handler, error);
                }
            });

说明:Android WebView组件加载网页发生证书认证错误时,会调用WebViewClient类的onReceivedSslError方法,在这个方法里,我们可以点击源码看到SslErrorHandler中有两个主要的方法可以调用

【1】cancel( )
停止加载问题页面

【2】proceed( )
忽略SSL证书错误,继续加载页面

如果我们不考虑证书安全,则可以直接这样写

webView.setWebViewClient(new WebViewClient ()
                @Override
                public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
                    handler.proceed();
                }
            });

当然了,既然用到了https,我们大部分时候都是为了网络安全问题
所以,可以定义一个类来验证证书安全,代码如下:

/**
 * 时间 :2018/1/11  10:17
 * 作者 :陈奇
 * 作用 :证书验证的方法
 */
public class CertifiUtils {
    // 验证证书
    public static void OnCertificateOfVerification(final SslErrorHandler handler, String url) {
        OkHttpClient.Builder builder = setCertificates(new OkHttpClient.Builder());
        Request request = new Request.Builder().url(url)
                .build();
        builder.build().newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                BNLog.e("证书验证失败", e.getMessage());
                handler.cancel();
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                BNLog.e("证书验证成功", response.body().string());
                handler.proceed();
            }
        });
    }

    private static OkHttpClient.Builder setCertificates(OkHttpClient.Builder client) {
        try {
            //Xutils.getSSLContext:获取证书SSLSocketFactory(这个网络上有很多代码,并且我之前的文章里也有写出来,在这里就不过多的描述了)
            SSLSocketFactory sslSocketFactory = Xutils.getSSLContext(BestnetApplication.contextApplication).getSocketFactory();
            X509TrustManager trustManager = Platform.get().trustManager(sslSocketFactory);
            client.sslSocketFactory(sslSocketFactory, trustManager);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return client;
    }
}

onReceivedSslError中的代码就可以这么写

webView.setWebViewClient(new WebViewClient ()
                @Override
                public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
                    CertifiUtils.OnCertificateOfVerification(handler,view.getUrl() );
                }
            });

但是,还有种情况是这样的,当webview加载url时,它并不调用onReceivedSslError方法,而是在onReceivedError方法里返回net::ERR_SSL_PROTOCOL_ERROR。原因呢,当时我调用是我们的链接,而这个链接则请求的是第三方请求,但是这个第三方请求必须要验证证书,这个时候如果没有验证证书,第三方请求就会返回一个错误给我们的链接,然后在返回到客户端。也就是说第三方直接否定了这次请求,这个请求呢,就不是我们的链接能控制的了。所以这个时候是调用的是onReceivedError,而不调用onReceivedSslError。只是,最根本的原理是,webview加载html资源,默认支持的是http请求而不是https请求。所以我们是不是只要把http请求替换成带验证证书的https请求就好了?当然,通过证明,这是可行的。

在WebViewClient 类中就提供了拦截的方法shouldInterceptRequest

shouldInterceptRequest有两种重载。
从API 11开始引入,API 21弃用
public WebResourceResponse shouldInterceptRequest (WebView view, String url)

从API 21开始引入
public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request)

综上所述,代码如下:

/**
 * 时间 :2018/1/19  18:24
 * 作者 :陈奇
 * 作用 :自定义webViewClient,拦截http(s)请求,并对相应的加载证书验证
 */
public class SslPinningWebViewClient extends WebViewClient {

    private SSLContext sslContext;

    public SslPinningWebViewClient() throws IOException {
        // 从封装好的方法中获取加载了证书的SSLContext
        sslContext = Xutils.getSSLContext(BestnetApplication.contextApplication);
    }

    @Override
    public WebResourceResponse shouldInterceptRequest(final WebView view, String url) {
        return processRequest(Uri.parse(url));
    }

    @Override
    @TargetApi(21)
    public WebResourceResponse shouldInterceptRequest(final WebView view, WebResourceRequest interceptedRequest) {
        return processRequest(interceptedRequest.getUrl());
    }

    private WebResourceResponse processRequest(Uri uri) {
        Log.d("SSL_PINNING_WEBVIEWS", "GET: " + uri.toString());

        try {
            // 定义获取的资源流,资源类型,资源编码
            InputStream is;
            String contentType;
            String encoding;
            // 设置一个url链接
            URL url = new URL(uri.toString());
            // 如果是http请求,这里如果加载的是http请求,则也要建立HttpURLConnection而不是默认加载。
            if (uri.toString().startsWith(APPConstants.CONFIGURATION_HTTP)) {
                HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
                is = httpURLConnection.getInputStream();
                contentType = httpURLConnection.getContentType();
                encoding = httpURLConnection.getContentEncoding();
            } else { // 如果是https请求
                HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
                urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
                is = urlConnection.getInputStream();
                contentType = urlConnection.getContentType();
                encoding = urlConnection.getContentEncoding();
            }

            // 如果信息头中的资源类型不为null,则继续
            if (contentType != null) {

                String mimeType = contentType;

                // 解析出mimeType
                if (contentType.contains(";")) {
                    mimeType = contentType.split(";")[0].trim();
                }

                Log.d("SSL_PINNING_WEBVIEWS", "Mime: " + mimeType);

                // 返回设置重新设置过的请求
                return new WebResourceResponse(mimeType, encoding, is);
            }

        } catch (SSLHandshakeException e) {
            e.printStackTrace();
            Log.d("SSL_PINNING_WEBVIEWS", e.getLocalizedMessage());
        } catch (Exception e) {
            e.printStackTrace();
            Log.d("SSL_PINNING_WEBVIEWS", e.getLocalizedMessage());
        }

        // 返回一个空的请求
        return new WebResourceResponse(null, null, null);
    }

    private boolean isCause(Class<? extends Throwable> expected, Throwable exc) {
        return expected.isInstance(exc) || (exc != null && isCause(expected, exc.getCause()));
    }
}

https加载http图片显示不出来:
1、设置getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);

2、或者使用上面的SslPinningWebViewClient

使用时是需要调用就可以了
webView.setWebViewClient(new SslPinningWebViewClient() {})

缺点:这种重写,虽然可以加载出https和http,但是如果这个h5页面中含有post带参请求,在拦截的时候是拦截不出来参数的。这就导致了h5中有的地方点击是有问题!!!

第二种方案:

/**
 * 时间 :2018/1/25  14:48
 * 作者 :陈奇
 * 作用 :自定义webViewClient,加载https请求
 */
public class BasicWebViewClientEx extends WebViewClient {
    private X509Certificate[] certificatesChain;
    private PrivateKey clientCertPrivateKey;
    public BasicWebViewClientEx() {
        initPrivateKeyAndX509Certificate(BestnetApplication.contextApplication);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @Override
    public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) {
        if ((null != clientCertPrivateKey) && ((null != certificatesChain) && (certificatesChain.length != 0))) {
            request.proceed(clientCertPrivateKey, certificatesChain);
        } else {
            request.cancel();
        }
    }

    private static final String KEY_STORE_CLIENT_PATH = "client.p12";//客户端要给服务器端认证的证书
    private static final String KEY_STORE_PASSWORD = "btydbg2018";// 客户端证书密码

    private void initPrivateKeyAndX509Certificate(Context context) {
        try {
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            InputStream ksIn = context.getResources().getAssets().open(KEY_STORE_CLIENT_PATH);
            keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray());
            Enumeration<?> localEnumeration;
            localEnumeration = keyStore.aliases();
            while (localEnumeration.hasMoreElements()) {
                String str3 = (String) localEnumeration.nextElement();
                clientCertPrivateKey = (PrivateKey) keyStore.getKey(str3, KEY_STORE_PASSWORD.toCharArray());
                if (clientCertPrivateKey == null) {
                    continue;
                } else {
                    Certificate[] arrayOfCertificate = keyStore.getCertificateChain(str3);
                    certificatesChain = new X509Certificate[arrayOfCertificate.length];
                    for (int j = 0; j < certificatesChain.length; j++) {
                        certificatesChain[j] = ((X509Certificate) arrayOfCertificate[j]);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onReceivedSslError(final WebView view, SslErrorHandler handler,
                                   SslError error) {
        handler.proceed();
    }

}

缺点:只支持API21及以上的。在网上也有一些说是可以导入安卓4.2的源码架包,然后作为系统架包依赖进去,可是这样,4.2系统以上的新的API就是用不了了。

此文只供参考,如有哪位大神有完美的解决方案,请艾特我,感谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,313评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,369评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,916评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,333评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,425评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,481评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,491评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,268评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,719评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,004评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,179评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,832评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,510评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,153评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,402评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,045评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,071评论 2 352

推荐阅读更多精彩内容