2025年3月29日 星期六 甲辰(龙)年 月廿八 设为首页 加入收藏
rss
您当前的位置:首页 > 计算机 > 编程开发 > 安卓(android)开发

Android 让WebView完美支持https双向认证(SSL)

时间:02-05来源:作者:点击数:35

最近做一个安全级别比较高的项目,对方要求使用HTTPS双向认证来访问web网页。双向认证在android5.0以上很好解决,但是在Android5.0以下,webviewclient中没有客户端向服务器发送证书的回调接口(回调是个隐藏函数)。

网上搜索到大概有这么几种解决方法:

1.利用反射调用隐藏函数(不太现实,这个方法为回调方法)

2.自己编译完整的class.jar(试过了,没成功,成本很大)

3.重写webview(不可能,工作量巨大)

经过上面的几种想法后来在网上高人的指点下有了第四种方法。

解决方法:拦截Webview的Request的请求,然后自己实现httpconnection捞取数据,然后返回新的WebResourceResponse给Webview。重写webviewclient中的shouldInterceptRequest方法即可。

下面我来给大家具体实现方法:

step1:生成两个证书

server.cer 服务器端证书

client.p12 客户端证书 (需要记住密码加入之后的java文件中)

将这两个证书放在 android assets 文件下。

step2:重写WebViewClient引入两个证书

SslPinningWebViewClient.java

  • package com.cloudhome.webview_https;
  • import android.annotation.TargetApi;
  • import android.content.Context;
  • import android.net.Uri;
  • import android.util.Base64;
  • import android.util.Log;
  • import android.webkit.WebResourceRequest;
  • import android.webkit.WebResourceResponse;
  • import android.webkit.WebView;
  • import android.webkit.WebViewClient;
  • import java.io.ByteArrayInputStream;
  • import java.io.IOException;
  • import java.io.InputStream;
  • import java.net.URL;
  • import java.security.KeyManagementException;
  • import java.security.KeyStore;
  • import java.security.KeyStoreException;
  • import java.security.NoSuchAlgorithmException;
  • import java.security.SecureRandom;
  • import java.security.UnrecoverableKeyException;
  • import java.security.cert.CertPathValidatorException;
  • import java.security.cert.CertificateException;
  • import java.security.cert.CertificateFactory;
  • import java.security.cert.X509Certificate;
  • import javax.net.ssl.HttpsURLConnection;
  • import javax.net.ssl.KeyManager;
  • import javax.net.ssl.KeyManagerFactory;
  • import javax.net.ssl.SSLContext;
  • import javax.net.ssl.SSLHandshakeException;
  • import javax.net.ssl.TrustManager;
  • import javax.net.ssl.TrustManagerFactory;
  • import javax.net.ssl.X509TrustManager;
  • /**
  • * Created by mennomorsink on 06/05/15.
  • */
  • public class SslPinningWebViewClient extends WebViewClient {
  • private LoadedListener listener;
  • private SSLContext sslContext;
  • public SslPinningWebViewClient(LoadedListener listener)throws IOException {
  • this.listener = listener;
  • prepareSslPinning(MyApplication.mContext.getResources().getAssets().open("server.cer"));
  • }
  • @Override
  • public WebResourceResponse shouldInterceptRequest (final WebView view, String url) {
  • if(MainActivity.pinningSwitch.isChecked()) {
  • return processRequest(Uri.parse(url));
  • } else {
  • return null;
  • }
  • }
  • @Override
  • @TargetApi(21)
  • public WebResourceResponse shouldInterceptRequest (final WebView view, WebResourceRequest interceptedRequest) {
  • if(MainActivity.pinningSwitch.isChecked()) {
  • return processRequest(interceptedRequest.getUrl());
  • } else {
  • return null;
  • }
  • }
  • private WebResourceResponse processRequest(Uri uri) {
  • Log.d("SSL_PINNING_WEBVIEWS", "GET: " + uri.toString());
  • try {
  • // Setup connection
  • URL url = new URL(uri.toString());
  • HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
  • // Set SSL Socket Factory for this request
  • urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
  • // Get content, contentType and encoding
  • InputStream is = urlConnection.getInputStream();
  • String contentType = urlConnection.getContentType();
  • String encoding = urlConnection.getContentEncoding();
  • // If got a contentType header
  • if(contentType != null) {
  • String mimeType = contentType;
  • // Parse mime type from contenttype string
  • if (contentType.contains(";")) {
  • mimeType = contentType.split(";")[0].trim();
  • }
  • Log.d("SSL_PINNING_WEBVIEWS", "Mime: " + mimeType);
  • listener.Loaded(uri.toString());
  • // Return the response
  • return new WebResourceResponse(mimeType, encoding, is);
  • }
  • } catch (SSLHandshakeException e) {
  • if(isCause(CertPathValidatorException.class, e)) {
  • listener.PinningPreventedLoading(uri.getHost());
  • }
  • Log.d("SSL_PINNING_WEBVIEWS", e.getLocalizedMessage());
  • } catch (Exception e) {
  • Log.d("SSL_PINNING_WEBVIEWS", e.getLocalizedMessage());
  • }
  • // Return empty response for this request
  • return new WebResourceResponse(null, null, null);
  • }
  • private void prepareSslPinning(InputStream... certificates)throws IOException {
  • try {
  • InputStream inputStream = MyApplication.mContext.getResources().getAssets().open("client.p12");
  • TrustManager[] trustManagers = prepareTrustManager(certificates);
  • KeyManager[] keyManagers = prepareKeyManager(inputStream, "your client.p12 password");
  • sslContext = SSLContext.getInstance("TLS");
  • sslContext.init(keyManagers, new TrustManager[]{new MyTrustManager(chooseTrustManager(trustManagers))}, new SecureRandom());
  • } catch (NoSuchAlgorithmException e) {
  • e.printStackTrace();
  • } catch (KeyStoreException e) {
  • e.printStackTrace();
  • } catch (KeyManagementException e) {
  • e.printStackTrace();
  • }
  • }
  • 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)
  • {
  • }
  • }
  • TrustManagerFactory trustManagerFactory = null;
  • trustManagerFactory = TrustManagerFactory.
  • getInstance(TrustManagerFactory.getDefaultAlgorithm());
  • trustManagerFactory.init(keyStore);
  • TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
  • return trustManagers;
  • } catch (NoSuchAlgorithmException e)
  • {
  • e.printStackTrace();
  • } catch (CertificateException e)
  • {
  • e.printStackTrace();
  • } catch (KeyStoreException e)
  • {
  • e.printStackTrace();
  • } catch (Exception e)
  • {
  • e.printStackTrace();
  • }
  • return null;
  • }
  • private static KeyManager[] prepareKeyManager(InputStream bksFile, String password)
  • {
  • try
  • {
  • if (bksFile == null || password == null) return null;
  • KeyStore clientKeyStore = KeyStore.getInstance("PKCS12");
  • 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;
  • }
  • private static X509TrustManager chooseTrustManager(TrustManager[] trustManagers)
  • {
  • for (TrustManager trustManager : trustManagers)
  • {
  • if (trustManager instanceof X509TrustManager)
  • {
  • return (X509TrustManager) trustManager;
  • }
  • }
  • return null;
  • }
  • 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];
  • }
  • }
  • public static boolean isCause(
  • Class<? extends Throwable> expected,
  • Throwable exc
  • ) {
  • return expected.isInstance(exc) || (
  • exc != null && isCause(expected, exc.getCause())
  • );
  • }
  • }

step3:重写的WebViewClient :SslPinningWebViewClient加入到webview中

  • package com.cloudhome.webview_https;
  • import android.content.Context;
  • import android.os.Bundle;
  • import android.support.v7.app.AppCompatActivity;
  • import android.view.View;
  • import android.webkit.WebView;
  • import android.widget.Button;
  • import android.widget.Switch;
  • import android.widget.TextView;
  • import java.io.IOException;
  • public class MainActivity extends AppCompatActivity {
  • private WebView webView;
  • public static Switch pinningSwitch;
  • private Button btnA;
  • private Button btnB;
  • public static TextView textView;
  • private String url1 = "your https url";
  • private String url2 = "your https url";
  • public MainActivity() {
  • }
  • public static Context mContext;
  • @Override
  • protected void onCreate(Bundle savedInstanceState) {
  • super.onCreate(savedInstanceState);
  • setContentView(R.layout.activity_main);
  • webView = (WebView)findViewById(R.id.webView);
  • webView.getSettings().setJavaScriptEnabled(true);
  • pinningSwitch = (Switch)findViewById(R.id.pinningSwitch);
  • btnA = (Button)findViewById(R.id.btn1);
  • btnB = (Button)findViewById(R.id.btn2);
  • textView = (TextView)findViewById(R.id.textView);
  • mContext=this;
  • SslPinningWebViewClient webViewClient = null;
  • try {
  • webViewClient = new SslPinningWebViewClient(new LoadedListener() {
  • @Override
  • public void Loaded(final String url) {
  • runOnUiThread(new Runnable() {
  • @Override
  • public void run() {
  • textView.setText("Loaded " + url);
  • }
  • });
  • }
  • @Override
  • public void PinningPreventedLoading(final String host) {
  • runOnUiThread(new Runnable() {
  • @Override
  • public void run() {
  • textView.setText("SSL Pinning prevented loading from " + host);
  • }
  • });
  • }
  • });
  • } catch (IOException e) {
  • e.printStackTrace();
  • }
  • webView.setWebViewClient(webViewClient);
  • btnA.setOnClickListener(new View.OnClickListener() {
  • @Override
  • public void onClick(View v) {
  • webView.clearView();
  • textView.setText("");
  • webView.loadUrl(url1);
  • }
  • });
  • btnB.setOnClickListener(new View.OnClickListener() {
  • @Override
  • public void onClick(View v) {
  • webView.clearView();
  • textView.setText("");
  • webView.loadUrl(url2);
  • }
  • });
  • }
  • }

step4:下载回调监听借口LoadedListener借口(根据实际情况使用)

  • package com.cloudhome.webview_https;
  • /**
  • * Created by mennomorsink on 25/07/15.
  • */
  • public interface LoadedListener {
  • void Loaded(String url);
  • void PinningPreventedLoading(String host);
  • }

运行效果:

开始前

运行中加载图片(链接中有其它https请求)

最后加载完成

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门