Android webview在https下实现ssl的双向认证

来源:互联网 发布:淘宝联盟淘宝身份认证 编辑:程序博客网 时间:2024/05/22 07:41

一、概述

1.简介

Https简单来说是Http的安全版,Https协议是由SSL+Http协议构建的可进行加密传输、身份认证的网络协议,比Http协议更加安全。

这里说的安全靠的就是SSL,SSL的作用如下:

  • a.认证用户和服务器,确保数据发送到正确的客户机和服务器。(验证证书)
  • b.加密数据防止传输数据中途被窃取。(加密)
  • c.维护数据的完整性,确保数据在传输过程中不被改变。(摘要算法)

Https在传输数据之前需要客户端与服务器端之间进行一次握手,只有握手通过了,才会有数据传输的过程。这个握手就是上面所说SSL作用的第一条,认证用户和服务器,这个认证的方式是用证书来实现的。

上面所述只是对Https做一个简单的介绍,更加全面具体的概述可以直接百度。

2.文章重点

这篇文章的重点是实现SSL证书双向认证,即客户端与服务器进行握手。

包含:

  • a.生成客户端与服务端证书。
  • b.搭建支持Https的服务器。
  • c.实现webview的双向证书认证。

二、生成证书

本篇文章用的是自签名证书,为什么不用权威机构颁发的证书?因为要钱!!!当然如果公司有去申请了最好。下面生成一个服务器端的自签名证书。

如何生成证书?在JDK中有个keytool工具,非常简单,只要安装了JDK都可以生成一个证书。直接命令行走起:

keytool -genkey -alias zx_server -keyalg RSA -keystore D:\key\zx_server.jks -validity 3600 -storepass 123456


使用上述的命令就可以在D盘的“key”文件夹生成一个服务器端的证书库文件 zx_server.jks,它的密钥库口令为:123456

接下来利用zx_server.jks证书库生成一个服务器端证书,它是可以被客户端拿来使用的:

keytool -export -alias zx_server -file D:\key\zx_server.cer -keystore D:\key\zx_server.jks -storepass 123456


生成了包含公钥的服务器端证书zx_server.cer。

三、下载tomcat,使用自签名证书配置Https

先去下载一个tomcat,本篇文章用的是tomcat 7,地址:http://tomcat.apache.org/download-70.cgi

压缩包接下后,找到tomcat/config/server.xml文件,以文本的形式打开。

在Servcie标签下,加入如下标签:

[html] view plain copy
  1. <Connector SSLEnabled="true"   
  2.             acceptCount="100"   
  3.             disableUploadTimeout="true"   
  4.             enableLookups="true"   
  5.             keystoreFile="D:/key/zx_server.jks"   
  6.             keystorePass="123456"   
  7.             maxSpareThreads="75"   
  8.             maxThreads="200"   
  9.             minSpareThreads="5" port="8443"   
  10.             protocol="org.apache.coyote.http11.Http11NioProtocol" scheme="https"   
  11.             secure="true"   
  12.             sslProtocol="TLS"  
  13.             clientAuth="false" />   

注意:keystoreFile的为刚刚生成zx_server.jks文件路径(这里填写自己的路径),keystorePass的值为密钥库口令:123456。

现在一个支持Https的tomcat服务器配好了,就是这么简单。找到tomcat/bin/startup.bat,直接双击启动服务器。

启动成功后,打开浏览器输入https://localhost:8443/ 即可看到证书不被信任的警告,无视它,直接进入,即可看见tomcat的默认主页。


四、webview实现双向认证

这步是本篇文章的重点。通过上述步骤一个https的服务器已经搭建好了,现在要实现双向认证,也就是说客户端也会有一个“jks文件”,服务器端要有一个“cer文件”与之对应,抽象来说就是客户端要去认证服务器端。

上面的步骤已经生成了zx_server.jks和zx_server.cer文件,按照生成上述证书的方式,生成两个客户端文件,命名为:zx_client.jks和zx_client.cer,现在去配置客户端证书。

1.配置服务器

服务器的配置非常简单,在上述第三步配置tomcat时添加的标签中,添加一些属性

[html] view plain copy
  1. <Connector SSLEnabled="true"   
  2.             acceptCount="100"   
  3.             disableUploadTimeout="true"   
  4.             enableLookups="true"   
  5.             keystoreFile="D:/key/zx_server.jks"   
  6.             keystorePass="123456"   
  7.             maxSpareThreads="75"   
  8.             maxThreads="200"   
  9.             minSpareThreads="5" port="8443"   
  10.             protocol="org.apache.coyote.http11.Http11NioProtocol" scheme="https"   
  11.             secure="true"   
  12.             sslProtocol="TLS"  
  13.             clientAuth="true"  
  14.             truststoreFile="D:/key/zx_client.cer" />   
上面的标签其它部分不变,只在标签的最后把clientAuth设置为true,同时多添加了一个属性truststoreFile,这个属性放的应该是我们刚刚生成的zx_client.cer文件,但加入后启动服务器会报错。现在我们把zx_client.cer这个文件添加在一个新生成的jks文件中:

keytool -import -alias D:\key\zx_client -file D:\key\zx_client.cer -keystore D:\key\zx_client_for_sever.jks


现在修改Server.xml文件,把truststoreFile属性的值改为刚刚生成的zx_client_for_sever.jks文件

[html] view plain copy
  1. <Connector //其它属性不变  
  2.             clientAuth="true"  
  3.             truststoreFile="D:/key/zx_client_for_sever.jks" />   
这时重新启动tomcat,用浏览器再次访问刚刚的地址,会发现“localhost 不接受您的登录证书,或者您的登录证书可能已过期。



现在用浏览器已经不能访问我们的服务器了,接下来我们来配置Android端,实现访问。

2.配置app

实现webview https的证书双向认证,说一下大概的实现思路:

  • a.代码中对证书做信任认证.
  • b.重写WebViewClient的shouldInterceptRequest方法,拦截WebView的Request请求,获取HttpsUrlConnection为其设置SSL的SocketFactory,利用这个HttpsUrlConnection拦截数据,然后返回新的WebResourceResponse给WebView。

按照上述思路开始之前先解决一个问题,Android平台只能识别bks格式的证书文件,现在我们就要把jks文件进行转换。

如何转换?方式很多,这里说一种,利用portecle,下载地址:https://sourceforge.net/projects/portecle/?source=typ_redirect

下载后解压文件,找到portecle.jar文件,直接双击即可运行,步骤基本上就是,选择要转换的jks文件->输入密码->选择转换的格式->保存文件,大体步骤如下:

  • a.选择jks文件


  • b.输入密码


  • c.选择转换的格式,会再次输入密码


  • d.提示转换成功,直接保存即可。



步骤很小白,还是贴出来了大体的步骤。

现在生产了一个zx_client.bks文件,再找到上面生产的zx_server.cer文件,将这两个文件放在Android assets文件夹下。

然后重写WebViewClient引入这两个证书,直接贴代码。

[java] view plain copy
  1. package com.zx.webview_ssl;  
  2.   
  3. import android.annotation.TargetApi;  
  4. import android.net.Uri;  
  5. import android.os.Build;  
  6. import android.webkit.WebResourceRequest;  
  7. import android.webkit.WebResourceResponse;  
  8. import android.webkit.WebView;  
  9. import android.webkit.WebViewClient;  
  10.   
  11. import java.io.IOException;  
  12. import java.io.InputStream;  
  13. import java.net.MalformedURLException;  
  14. import java.net.URL;  
  15. import java.security.KeyManagementException;  
  16. import java.security.KeyStore;  
  17. import java.security.KeyStoreException;  
  18. import java.security.NoSuchAlgorithmException;  
  19. import java.security.SecureRandom;  
  20. import java.security.UnrecoverableKeyException;  
  21. import java.security.cert.CertificateException;  
  22. import java.security.cert.CertificateFactory;  
  23. import java.security.cert.X509Certificate;  
  24.   
  25. import javax.net.ssl.HostnameVerifier;  
  26. import javax.net.ssl.HttpsURLConnection;  
  27. import javax.net.ssl.KeyManager;  
  28. import javax.net.ssl.KeyManagerFactory;  
  29. import javax.net.ssl.SSLContext;  
  30. import javax.net.ssl.SSLSession;  
  31. import javax.net.ssl.TrustManager;  
  32. import javax.net.ssl.TrustManagerFactory;  
  33. import javax.net.ssl.X509TrustManager;  
  34.   
  35. public class SslWebViewClient extends WebViewClient {  
  36.   
  37.     private SSLContext sslContext;  
  38.   
  39.     public SslWebViewClient() {  
  40.         try {  
  41.             TrustManager[] trustManagers = prepareTrustManager(MyApplication.mContext.getResources().getAssets().open("zx_server.cer"));  
  42.             KeyManager[] keyManagers = prepareKeyManager(MyApplication.mContext.getResources().getAssets().open("zx_client.bks"), "123456");  
  43.             sslContext = SSLContext.getInstance("TLS");  
  44.             X509TrustManager trustManager = null;  
  45.             if (null != trustManagers){  
  46.                 trustManager = new MyTrustManager(chooseTrustManager(trustManagers));  
  47.             }else {  
  48.                 trustManager = new UnSafeTrustManager();  
  49.             }  
  50.             sslContext.init(keyManagers, new TrustManager[]{trustManager}, new SecureRandom());  
  51.         } catch (IOException e) {  
  52.             e.printStackTrace();  
  53.         } catch (NoSuchAlgorithmException e) {  
  54.             e.printStackTrace();  
  55.         } catch (KeyStoreException e) {  
  56.             e.printStackTrace();  
  57.         } catch (KeyManagementException e) {  
  58.             e.printStackTrace();  
  59.         }  
  60.     }  
  61.   
  62.     @Override  
  63.     public WebResourceResponse shouldInterceptRequest(WebView view, String url) {  
  64.         return processRequest(Uri.parse(url));  
  65.     }  
  66.   
  67.     @TargetApi(Build.VERSION_CODES.LOLLIPOP)  
  68.     @Override  
  69.     public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {  
  70.         return processRequest(request.getUrl());  
  71.     }  
  72.   
  73.     private WebResourceResponse processRequest(Uri uri) {  
  74.         try {  
  75.             //设置连接  
  76.             URL url = new URL(uri.toString());  
  77.             HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();  
  78.             //为request设置SSL Socket Factory  
  79.             urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());  
  80.   
  81.             urlConnection.setHostnameVerifier(new HostnameVerifier() {  
  82.                 @Override  
  83.                 public boolean verify(String hostname, SSLSession session) {  
  84.                     return true;  
  85.                 }  
  86.             });  
  87.   
  88.             //获取请求的内容、contentType、encoding  
  89.             InputStream inputStream = urlConnection.getInputStream();  
  90.             String contentType = urlConnection.getContentType();  
  91.             String encoding = urlConnection.getContentEncoding();  
  92.             if (null != contentType){  
  93.                 String mimeType = contentType;  
  94.                 if (contentType.contains(";")){  
  95.                     mimeType = contentType.split(";")[0].trim();  
  96.                 }  
  97.                 //返回新的response  
  98.                 return new WebResourceResponse(mimeType, encoding, inputStream);  
  99.             }  
  100.   
  101.         } catch (MalformedURLException e) {  
  102.             e.printStackTrace();  
  103.         } catch (IOException e) {  
  104.             e.printStackTrace();  
  105.         }  
  106.         return null;  
  107.     }  
  108.   
  109.     private TrustManager[] prepareTrustManager(InputStream... certificates) {  
  110.         if (certificates == null || certificates.length <= 0){  
  111.             return null;  
  112.         }  
  113.         try {  
  114.             CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");  
  115.             KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());  
  116.             keyStore.load(null);  
  117.             int index = 0;  
  118.             for (InputStream certificate : certificates) {  
  119.                 String certificateAlias = Integer.toString(index++);  
  120.                 keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));  
  121.                 try {  
  122.                     if (certificate != null)  
  123.                         certificate.close();  
  124.                 } catch (IOException e){  
  125.   
  126.                 }  
  127.             }  
  128.             TrustManagerFactory trustManagerFactory = null;  
  129.             trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());  
  130.             trustManagerFactory.init(keyStore);  
  131.             TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();  
  132.             return trustManagers;  
  133.         } catch (NoSuchAlgorithmException e) {  
  134.             e.printStackTrace();  
  135.         } catch (CertificateException e) {  
  136.             e.printStackTrace();  
  137.         } catch (KeyStoreException e) {  
  138.             e.printStackTrace();  
  139.         } catch (Exception e) {  
  140.             e.printStackTrace();  
  141.         }  
  142.         return null;  
  143.   
  144.     }  
  145.   
  146.     private KeyManager[] prepareKeyManager(InputStream bksFile, String password) {  
  147.         try {  
  148.             if (bksFile == null || password == null){  
  149.                 return null;  
  150.             }  
  151.             KeyStore clientKeyStore = KeyStore.getInstance("BKS");  
  152.             clientKeyStore.load(bksFile, password.toCharArray());  
  153.             KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());  
  154.             keyManagerFactory.init(clientKeyStore, password.toCharArray());  
  155.             return keyManagerFactory.getKeyManagers();  
  156.         } catch (KeyStoreException e) {  
  157.             e.printStackTrace();  
  158.         } catch (NoSuchAlgorithmException e) {  
  159.             e.printStackTrace();  
  160.         } catch (UnrecoverableKeyException e) {  
  161.             e.printStackTrace();  
  162.         } catch (CertificateException e) {  
  163.             e.printStackTrace();  
  164.         } catch (IOException e) {  
  165.             e.printStackTrace();  
  166.         } catch (Exception e) {  
  167.             e.printStackTrace();  
  168.         }  
  169.         return null;  
  170.     }  
  171.   
  172.     private static X509TrustManager chooseTrustManager(TrustManager[] trustManagers) {  
  173.         for (TrustManager trustManager : trustManagers) {  
  174.             if (trustManager instanceof X509TrustManager) {  
  175.                 return (X509TrustManager) trustManager;  
  176.             }  
  177.         }  
  178.         return null;  
  179.     }  
  180.   
  181.     public static class MyTrustManager implements X509TrustManager{  
  182.         private X509TrustManager defaultTrustManager;  
  183.         private X509TrustManager localTrustManager;  
  184.   
  185.         public MyTrustManager(X509TrustManager localTrustManager) throws NoSuchAlgorithmException, KeyStoreException {  
  186.             TrustManagerFactory var4 = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());  
  187.             var4.init((KeyStore) null);  
  188.             defaultTrustManager = chooseTrustManager(var4.getTrustManagers());  
  189.             this.localTrustManager = localTrustManager;  
  190.         }  
  191.   
  192.         @Override  
  193.         public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {  
  194.   
  195.         }  
  196.   
  197.         @Override  
  198.         public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {  
  199.             try {  
  200.                 defaultTrustManager.checkServerTrusted(chain, authType);  
  201.             } catch (CertificateException ce) {  
  202.                 localTrustManager.checkServerTrusted(chain, authType);  
  203.             }  
  204.         }  
  205.   
  206.         @Override  
  207.         public X509Certificate[] getAcceptedIssuers() {  
  208.             return new X509Certificate[0];  
  209.         }  
  210.     }  
  211.   
  212.     public static class UnSafeTrustManager implements X509TrustManager {  
  213.         @Override  
  214.         public void checkClientTrusted(X509Certificate[] chain, String authType)  
  215.                 throws CertificateException {  
  216.         }  
  217.   
  218.         @Override  
  219.         public void checkServerTrusted(X509Certificate[] chain, String authType)  
  220.                 throws CertificateException {  
  221.         }  
  222.   
  223.         @Override  
  224.         public X509Certificate[] getAcceptedIssuers() {  
  225.             return new java.security.cert.X509Certificate[]{};  
  226.         }  
  227.     }  
  228.   
  229.   
  230. }  

代码很长一段,简单说明一下,核心部分是41行与42行代码分别调用的prepareTrustManager与prepareKeyManager方法,这两个方法分别对zx_client.cer与zx_client.bks证书初始化,然后在50行代码对两个证书建立联系,然后在79行代码为HttpsUrlConnection设置SSLSocketFactory,这个SSLSocketFactory是从第50行初始化的SSLContext中获取,此时的请求是安全的了,最后在第98行返回新的response。这样下来基本上就实现了刚刚在前面说的思路,从而实现WebView的SSL双向认证。

最后我们来看一下在手机上的运行效果:


可以看到我们已经成功访问到了tomcat的主页。

五、最后

其实只有极少数的应用需要双向认证,比如一些银行或者金融类的app。到此本篇文章结束,只要按照上述步骤下来就可以实现https下webview的SSL双向认证。

源码:https://github.com/xunzzz/WebView_SSL

阅读全文
0 0
原创粉丝点击