最近做一个安全级别比较高的项目,对方要求使用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请求)
最后加载完成