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

来源:互联网 发布:拼模特卡软件 编辑:程序博客网 时间:2024/05/01 06:43

转自:http://blog.csdn.net/kpioneer123/article/details/51491739

 这是@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


[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.cloudhome.webview_https;  
  2.   
  3. import android.annotation.TargetApi;  
  4. import android.content.Context;  
  5. import android.net.Uri;  
  6. import android.util.Base64;  
  7. import android.util.Log;  
  8. import android.webkit.WebResourceRequest;  
  9. import android.webkit.WebResourceResponse;  
  10. import android.webkit.WebView;  
  11. import android.webkit.WebViewClient;  
  12.   
  13. import java.io.ByteArrayInputStream;  
  14. import java.io.IOException;  
  15. import java.io.InputStream;  
  16. import java.net.URL;  
  17. import java.security.KeyManagementException;  
  18. import java.security.KeyStore;  
  19. import java.security.KeyStoreException;  
  20. import java.security.NoSuchAlgorithmException;  
  21. import java.security.SecureRandom;  
  22. import java.security.UnrecoverableKeyException;  
  23. import java.security.cert.CertPathValidatorException;  
  24. import java.security.cert.CertificateException;  
  25. import java.security.cert.CertificateFactory;  
  26. import java.security.cert.X509Certificate;  
  27.   
  28. import javax.net.ssl.HttpsURLConnection;  
  29. import javax.net.ssl.KeyManager;  
  30. import javax.net.ssl.KeyManagerFactory;  
  31. import javax.net.ssl.SSLContext;  
  32. import javax.net.ssl.SSLHandshakeException;  
  33. import javax.net.ssl.TrustManager;  
  34. import javax.net.ssl.TrustManagerFactory;  
  35. import javax.net.ssl.X509TrustManager;  
  36.   
  37. /** 
  38.  * Created by mennomorsink on 06/05/15. 
  39.  */  
  40. public class SslPinningWebViewClient extends WebViewClient {  
  41.   
  42.     private LoadedListener listener;  
  43.     private SSLContext sslContext;  
  44.   
  45.     public SslPinningWebViewClient(LoadedListener listener)throws IOException  {  
  46.         this.listener = listener;  
  47.   
  48.         prepareSslPinning(MyApplication.mContext.getResources().getAssets().open("server.cer"));  
  49.     }  
  50.   
  51.     @Override  
  52.     public WebResourceResponse shouldInterceptRequest (final WebView view, String url) {  
  53.         if(MainActivity.pinningSwitch.isChecked()) {  
  54.             return processRequest(Uri.parse(url));  
  55.         } else {  
  56.             return null;  
  57.         }  
  58.     }  
  59.   
  60.     @Override  
  61.     @TargetApi(21)  
  62.     public WebResourceResponse shouldInterceptRequest (final WebView view, WebResourceRequest interceptedRequest) {  
  63.         if(MainActivity.pinningSwitch.isChecked()) {  
  64.             return processRequest(interceptedRequest.getUrl());  
  65.         } else {  
  66.             return null;  
  67.         }  
  68.     }  
  69.   
  70.     private WebResourceResponse processRequest(Uri uri) {  
  71.         Log.d("SSL_PINNING_WEBVIEWS""GET: " + uri.toString());  
  72.   
  73.         try {  
  74.             // Setup connection  
  75.             URL url = new URL(uri.toString());  
  76.             HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();  
  77.   
  78.             // Set SSL Socket Factory for this request  
  79.             urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());  
  80.   
  81.             // Get content, contentType and encoding  
  82.             InputStream is = urlConnection.getInputStream();  
  83.             String contentType = urlConnection.getContentType();  
  84.             String encoding = urlConnection.getContentEncoding();  
  85.   
  86.             // If got a contentType header  
  87.             if(contentType != null) {  
  88.   
  89.                 String mimeType = contentType;  
  90.   
  91.                 // Parse mime type from contenttype string  
  92.                 if (contentType.contains(";")) {  
  93.                     mimeType = contentType.split(";")[0].trim();  
  94.                 }  
  95.   
  96.                 Log.d("SSL_PINNING_WEBVIEWS""Mime: " + mimeType);  
  97.   
  98.                 listener.Loaded(uri.toString());  
  99.   
  100.                 // Return the response  
  101.                 return new WebResourceResponse(mimeType, encoding, is);  
  102.             }  
  103.   
  104.         } catch (SSLHandshakeException e) {  
  105.             if(isCause(CertPathValidatorException.class, e)) {  
  106.                 listener.PinningPreventedLoading(uri.getHost());  
  107.             }  
  108.             Log.d("SSL_PINNING_WEBVIEWS", e.getLocalizedMessage());  
  109.         } catch (Exception e) {  
  110.             Log.d("SSL_PINNING_WEBVIEWS", e.getLocalizedMessage());  
  111.         }  
  112.   
  113.         // Return empty response for this request  
  114.         return new WebResourceResponse(nullnullnull);  
  115.     }  
  116.   
  117.     private void prepareSslPinning(InputStream... certificates)throws IOException {  
  118.   
  119.         try {  
  120.   
  121.   
  122.   
  123.             InputStream inputStream = MyApplication.mContext.getResources().getAssets().open("client.p12");  
  124.   
  125.             TrustManager[] trustManagers = prepareTrustManager(certificates);  
  126.             KeyManager[] keyManagers = prepareKeyManager(inputStream, "your client.p12 password");  
  127.   
  128.             sslContext = SSLContext.getInstance("TLS");  
  129.   
  130.             sslContext.init(keyManagers, new TrustManager[]{new MyTrustManager(chooseTrustManager(trustManagers))}, new SecureRandom());  
  131.   
  132.   
  133.         } catch (NoSuchAlgorithmException e) {  
  134.             e.printStackTrace();  
  135.         } catch (KeyStoreException e) {  
  136.             e.printStackTrace();  
  137.         } catch (KeyManagementException e) {  
  138.             e.printStackTrace();  
  139.         }  
  140.     }  
  141.   
  142.   
  143.   
  144.   
  145.     private static TrustManager[] prepareTrustManager(InputStream... certificates)  
  146.     {  
  147.         if (certificates == null || certificates.length <= 0return null;  
  148.         try  
  149.         {  
  150.   
  151.             CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");  
  152.             KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());  
  153.             keyStore.load(null);  
  154.             int index = 0;  
  155.             for (InputStream certificate : certificates)  
  156.             {  
  157.                 String certificateAlias = Integer.toString(index++);  
  158.                 keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));  
  159.                 try  
  160.                 {  
  161.                     if (certificate != null)  
  162.                         certificate.close();  
  163.                 } catch (IOException e)  
  164.   
  165.                 {  
  166.                 }  
  167.             }  
  168.             TrustManagerFactory trustManagerFactory = null;  
  169.   
  170.             trustManagerFactory = TrustManagerFactory.  
  171.                     getInstance(TrustManagerFactory.getDefaultAlgorithm());  
  172.             trustManagerFactory.init(keyStore);  
  173.   
  174.             TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();  
  175.   
  176.             return trustManagers;  
  177.         } catch (NoSuchAlgorithmException e)  
  178.         {  
  179.             e.printStackTrace();  
  180.         } catch (CertificateException e)  
  181.         {  
  182.             e.printStackTrace();  
  183.         } catch (KeyStoreException e)  
  184.         {  
  185.             e.printStackTrace();  
  186.         } catch (Exception e)  
  187.         {  
  188.             e.printStackTrace();  
  189.         }  
  190.         return null;  
  191.   
  192.     }  
  193.   
  194.     private static KeyManager[] prepareKeyManager(InputStream bksFile, String password)  
  195.     {  
  196.         try  
  197.         {  
  198.             if (bksFile == null || password == nullreturn null;  
  199.   
  200.             KeyStore clientKeyStore = KeyStore.getInstance("PKCS12");  
  201.             clientKeyStore.load(bksFile, password.toCharArray());  
  202.             KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());  
  203.             keyManagerFactory.init(clientKeyStore, password.toCharArray());  
  204.             return keyManagerFactory.getKeyManagers();  
  205.   
  206.         } catch (KeyStoreException e)  
  207.         {  
  208.             e.printStackTrace();  
  209.         } catch (NoSuchAlgorithmException e)  
  210.         {  
  211.             e.printStackTrace();  
  212.         } catch (UnrecoverableKeyException e)  
  213.         {  
  214.             e.printStackTrace();  
  215.         } catch (CertificateException e)  
  216.         {  
  217.             e.printStackTrace();  
  218.         } catch (IOException e)  
  219.         {  
  220.             e.printStackTrace();  
  221.         } catch (Exception e)  
  222.         {  
  223.             e.printStackTrace();  
  224.         }  
  225.         return null;  
  226.     }  
  227.   
  228.     private static X509TrustManager chooseTrustManager(TrustManager[] trustManagers)  
  229.     {  
  230.         for (TrustManager trustManager : trustManagers)  
  231.         {  
  232.             if (trustManager instanceof X509TrustManager)  
  233.             {  
  234.                 return (X509TrustManager) trustManager;  
  235.             }  
  236.         }  
  237.         return null;  
  238.     }  
  239.   
  240.   
  241.     private static class MyTrustManager implements X509TrustManager  
  242.     {  
  243.         private X509TrustManager defaultTrustManager;  
  244.         private X509TrustManager localTrustManager;  
  245.   
  246.         public MyTrustManager(X509TrustManager localTrustManager) throws NoSuchAlgorithmException, KeyStoreException  
  247.         {  
  248.             TrustManagerFactory var4 = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());  
  249.             var4.init((KeyStore) null);  
  250.             defaultTrustManager = chooseTrustManager(var4.getTrustManagers());  
  251.             this.localTrustManager = localTrustManager;  
  252.         }  
  253.   
  254.   
  255.         @Override  
  256.         public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException  
  257.         {  
  258.   
  259.         }  
  260.   
  261.         @Override  
  262.         public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException  
  263.         {  
  264.             try  
  265.             {  
  266.                 defaultTrustManager.checkServerTrusted(chain, authType);  
  267.             } catch (CertificateException ce)  
  268.             {  
  269.                 localTrustManager.checkServerTrusted(chain, authType);  
  270.             }  
  271.         }  
  272.   
  273.   
  274.         @Override  
  275.         public X509Certificate[] getAcceptedIssuers()  
  276.         {  
  277.             return new X509Certificate[0];  
  278.         }  
  279.     }  
  280.   
  281.     public static boolean isCause(  
  282.             Class<? extends Throwable> expected,  
  283.             Throwable exc  
  284.     ) {  
  285.         return expected.isInstance(exc) || (  
  286.                 exc != null && isCause(expected, exc.getCause())  
  287.         );  
  288.     }  
  289. }  


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


[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.cloudhome.webview_https;  
  2.   
  3. import android.content.Context;  
  4. import android.os.Bundle;  
  5. import android.support.v7.app.AppCompatActivity;  
  6. import android.view.View;  
  7. import android.webkit.WebView;  
  8. import android.widget.Button;  
  9. import android.widget.Switch;  
  10. import android.widget.TextView;  
  11.   
  12. import java.io.IOException;  
  13.   
  14. public class MainActivity extends AppCompatActivity {  
  15.   
  16.     private WebView webView;  
  17.     public static Switch pinningSwitch;  
  18.     private Button btnA;  
  19.     private Button btnB;  
  20.     public static TextView textView;  
  21.   
  22.     private String url1 = "your https url";  
  23.     private String url2 = "your https url";  
  24.   
  25.     public MainActivity() {  
  26.     }  
  27.     public static Context mContext;  
  28.     @Override  
  29.     protected void onCreate(Bundle savedInstanceState) {  
  30.         super.onCreate(savedInstanceState);  
  31.         setContentView(R.layout.activity_main);  
  32.   
  33.         webView = (WebView)findViewById(R.id.webView);  
  34.         webView.getSettings().setJavaScriptEnabled(true);  
  35.   
  36.         pinningSwitch = (Switch)findViewById(R.id.pinningSwitch);  
  37.         btnA = (Button)findViewById(R.id.btn1);  
  38.         btnB = (Button)findViewById(R.id.btn2);  
  39.         textView = (TextView)findViewById(R.id.textView);  
  40.   
  41.         mContext=this;  
  42.         SslPinningWebViewClient webViewClient = null;  
  43.         try {  
  44.             webViewClient = new SslPinningWebViewClient(new LoadedListener() {  
  45.                 @Override  
  46.                 public void Loaded(final String url) {  
  47.                     runOnUiThread(new Runnable() {  
  48.                         @Override  
  49.                         public void run() {  
  50.                             textView.setText("Loaded " + url);  
  51.                         }  
  52.                     });  
  53.                 }  
  54.   
  55.                 @Override  
  56.                 public void PinningPreventedLoading(final String host) {  
  57.                     runOnUiThread(new Runnable() {  
  58.                         @Override  
  59.                         public void run() {  
  60.                             textView.setText("SSL Pinning prevented loading from " + host);  
  61.                         }  
  62.                     });  
  63.                 }  
  64.             });  
  65.         } catch (IOException e) {  
  66.             e.printStackTrace();  
  67.         }  
  68.         webView.setWebViewClient(webViewClient);  
  69.   
  70.         btnA.setOnClickListener(new View.OnClickListener() {  
  71.              @Override  
  72.              public void onClick(View v) {  
  73.                  webView.clearView();  
  74.                  textView.setText("");  
  75.                  webView.loadUrl(url1);  
  76.              }  
  77.          });  
  78.   
  79.         btnB.setOnClickListener(new View.OnClickListener() {  
  80.             @Override  
  81.             public void onClick(View v) {  
  82.                 webView.clearView();  
  83.                 textView.setText("");  
  84.                 webView.loadUrl(url2);  
  85.             }  
  86.         });  
  87.     }  
  88. }  


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


[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.cloudhome.webview_https;  
  2.   
  3. /** 
  4.  * Created by mennomorsink on 25/07/15. 
  5.  */  
  6. public interface LoadedListener {  
  7.     void Loaded(String url);  
  8.     void PinningPreventedLoading(String host);  
  9. }  


运行效果:

开始前



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


最后加载完成




参考资料:

http://blog.csdn.net/lmj623565791/article/details/48129405

http://blog.csdn.net/zhangyong125/article/details/50562865


0 0
原创粉丝点击