Android 让WebView完美支持https双向认证(SSL)
来源:互联网 发布:lazada平台知乎 编辑:程序博客网 时间:2024/05/22 02:01
这是@happyzhang0502 关于webview https的建议:
最近做一个安全级别比较高的项目,对方要求使用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请求)
最后加载完成
参考资料:
http://blog.csdn.net/lmj623565791/article/details/48129405
http://blog.csdn.net/zhangyong125/article/details/50562865
- Android 让WebView完美支持https双向认证(SSL)
- Android 让WebView完美支持https双向认证(SSL)
- Android 让WebView完美支持https双向认证(SSL)
- android 让webview支持https 双向认证(SSL)
- android 让webview支持https 双向认证(SSL)
- android 让webview支持https 双向认证(SSL)
- android 让webview支持https 双向认证(SSL)
- Android webview在https下实现ssl的双向认证
- Android webview在https下实现ssl的双向认证
- Android支持https的处理方式3---webview支持https 双向认证
- android SSL证书认证、SSL双向认证、https访问;
- HTTPS的SSL双向认证
- IOS Android Tomcat SSL双向认证HTTPS访问
- IOS Android Tomcat SSL双向认证HTTPS访问
- Android Https 双向认证
- Android Https 双向认证
- Java keytool Tomcat https 双向认证 支持android bks
- TOMCAT SSL 双向认证配置(HTTPS)
- 中断,异常,系统调用
- STM32 通用定时器的一些概念解析
- java 中 阻塞队列 非阻塞队列 和普通队列的区别是什么?
- Adding a new MAVLink Message (Code Overview)
- 动态加载的资源一定要直接放在Resource目录下吗
- Android 让WebView完美支持https双向认证(SSL)
- Java中单链表的部分操作总结
- cookie和session的区别初步认识
- dul恢复drop表测试
- Adding Support for a new MAVLink Gimbal
- SNMP 服务启用方法
- 详解WMware Workstation的三种网络配置方式
- 最近做easy-ui要用到的formatter()
- linux驱动移植(nand,yaffs2) MTD分区