原文地址:https://my.oschina.net/u/157514/blog/395238
之前一篇中说了如何 建立 https 通信的完整流程,其中涉及了java web容器 tomcat,关于它的配置是:
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" SSLEnabled="true" maxThreads="150" scheme="https" secure="true"clientAuth="true" sslProtocol="TLS" keystoreFile="D:\ssl\server.keystore" keystorePass="123456" truststoreFile="D:\ssl\server.keystore" truststorePass="123456"/>
对应的keyStore(包括私钥库和受信任证书库)是在tomcat启动时一次性加载到内存中的。
大多数的场景这就够了,但是如果 要构建一个 基于https、受信任证书的 权限验证体系,像上边的那样 一次性加载 keyStore 就算 我们从键库中删除某个证书,程序是没办法探知的。我急需一种热加载 keyStore的技术。
我们修改对应的配置为
<Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol" SSLEnabled="true"maxThreads="150" scheme="https" secure="true"clientAuth="true" sslProtocol="TLS" keystoreFile="d:/ssl/server.keystore" keystorePass="123456" trustManagerClassName="MyTrustManager"/>
这里 自己的私钥键库是不需要也是不能重新加载的,关键在与证书键库,我们创建了一个在自定义的类叫
MyTrustManager 用来管理受信任证书,他需要实现接口 X509TrustManager 并提供一个无参的构造函数。
然后将其打包成jar后,部署到tomcat的lib目录(注意connector是在tomcat启动时构建的,所以代码不能放在你自己的web项目里,因为当时tomcat还没有classLoad你的类呢)下边是参考代码
public class MyTrustManager implements X509TrustManager {public MyTrustManager(){}@Overridepublic void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {System.out.println("check");if(x509Certificates==null||x509Certificates.length==0||s==null||s.length()==0) throw new IllegalArgumentException();KeyStore store=getKeyStore();boolean pass=false;try {for(X509Certificate certificate:x509Certificates){certificate.checkValidity();String theAlias = store.getCertificateAlias(certificate);if(theAlias!=null)pass=true;}} catch (KeyStoreException e) {e.printStackTrace();}System.out.println("pass "+pass);if(!pass)throw new CertificateException();}@Overridepublic void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {if(x509Certificates==null||x509Certificates.length==0||s==null||s.length()==0) throw new IllegalArgumentException();for(X509Certificate certificate:x509Certificates){certificate.checkValidity();}}@Overridepublic X509Certificate[] getAcceptedIssuers() {ArrayList<X509Certificate> trusts=new ArrayList<X509Certificate>();try {KeyStore store=getKeyStore();Enumeration<String> alias = store.aliases();while (alias.hasMoreElements()){String name = alias.nextElement();if(store.isCertificateEntry(name)){X509Certificate trust = (X509Certificate) store.getCertificate(name);trusts.add(trust);}}} catch (Exception e) {e.printStackTrace();}X509Certificate[] trustsArr = trusts.toArray(new X509Certificate[0]);System.out.println("return trust array "+trustsArr.length);return trustsArr;}private KeyStore getKeyStore() throws CertificateException {try {KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType());store.load(new FileInputStream("D:/ssl/server.keystore"),"123456".toCharArray());return store;} catch (Exception e) {e.printStackTrace();throw new CertificateException();}}
}
getAcceptedIssuers 返回server端的所有信任的证书,这样client (比如浏览器)才知道应该挑选哪些它所拥有的证书来询问你,证书必须是双方都认识的。
checkClientTrusted 是对client提交过来的证书进行验证,certificate.checkValidity();验证证书是否过期,store.getCertificateAlias(certificate)从自己的键库中寻找对应的证书,不存在返回null,发现验证不通过 就自己手动抛出一个异常CertificateException,这样容器就知道如何处理了。
getKeyStore中是加载键库,由于每次验证时才加载键库,所以就实现了热加载。当然为了性能你可以把keyStore放到某个全局变量里,在需要的时候对其进行reload。
综上所述,你可以实现一个通过添加信任证书的方式来对请求方进行控制的系统了。
注意:https为了性能在建立了SSL-SESSION之后允许不再进行证书验证,你在浏览器里访问做实验需要关闭浏览器后重新访问才会看到keyStore更新生效,你也可以通过代码设法销毁SSL-SESSION让每次https请求都重新握手验证。参考:http://blog.csdn.net/ibznphone/article/details/7846879