花了两三天终于搞完,中间有个环节卡了一天,不然一天就能搞定了。具体https、ssl是啥,我就不说了哈,不懂的先去百度。
废话不多说,下面开始正题。
使用tomcat8+jdk8。
一、单向
1、生成服务器证书库,及秘钥,keytool是jdk自带的,也可以使用openssl。
keytool-genkey -v -alias mykeystore -keyalg RSA -keystore server.keystore -validity 36500
2、查看证书库keytool -list -v -keystore server.keystore
3、导出公钥证书,用于浏览器安装
keytool -export -alias mykeystore -keystore server.keystore -rfc -file client.cer,名称什么的怕弄混,自己看着改吧。
生成之后点击安装,直接按照默认的就行了,正常都没问题,不放心可以再多安装几个目录。这个证书也可以不安装,不安装你就需要在浏览器上面点击信任继续访问什么的,就跟之前12306的那个网站的呢。
4、配置tomcat
<ConnectorSSLEnabled="true" acceptCount="100"clientAuth="false"
disableUploadTimeout="true"enableLookups="false"maxThreads="25"
port="8443"keystoreFile="/etc/starry/starryThc/server.keystore" keystorePass="123456"
protocol="org.apache.coyote.http11.Http11NioProtocol" scheme="https"
secure="true"sslProtocol="TLS" />
访问:
再访问,变成绿色的了
通过上面两张截图可以看出,在使用hosts配置局域网的这个主机名之后,浏览器认为他是安全的了。
好了,单向的SSL,浏览器端已经没有问题了,现在来写java后台实现访问。
5、java后台访问
不带证书的post
提示没有证书
跟我后台访问百度是一个样子。没有证书。
这个错误的原因是,jdk的证书信任库中没有该证书,为什么后台访问淘宝的没问题,就是因为淘宝使用的是CA的证书,而百度使用的是verisign家签名的证书,这个证书在浏览器中的是有的,但是在我们的jdk中是没有的,所以在浏览器没有问题,后台进行访问有问题。
下图为百度在浏览器中的证书。解决这个问题,有两个办法:1代码中带上公钥证书,2将公钥证书导入到jdk的信任库中。
1、编写代码,代码中带入服务器公钥证书
KeyStore trustKeyStore=KeyStore.getInstance("JKS"); trustKeyStore.load(new FileInputStream(trustStorePath), trustStorePassword.toCharArray()); trustManagerFactory.init(trustKeyStore); TrustManager[] trustManagers= trustManagerFactory.getTrustManagers();获取公钥证书。
public String doPostsWithOwnCer(String url,String params,String type,String charset) { PrintWriter out = null; InputStream in = null; String result = null; try { String trustStorePath = "C:\\Users\\Administrator\\Desktop\\学习记录\\keytool\\2018-07-09双向重新配置\\truststore.keystore"; String trustStorePassword = "123456"; // 创建SSLContext对象,并使用我们指定的信任管理器初始化 TrustManagerFactory trustManagerFactory=TrustManagerFactory.getInstance("SunX509"); KeyStore trustKeyStore=KeyStore.getInstance("JKS"); trustKeyStore.load(new FileInputStream(trustStorePath), trustStorePassword.toCharArray()); trustManagerFactory.init(trustKeyStore); TrustManager[] trustManagers= trustManagerFactory.getTrustManagers(); SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); sslContext.init(null, trustManagers, new java.security.SecureRandom()); // 从上述SSLContext对象中得到SSLSocketFactory对象 SSLSocketFactory ssf = sslContext.getSocketFactory(); URL uri = new URL(url); HttpsURLConnection urlcon = (HttpsURLConnection) uri.openConnection(); urlcon.setDoInput(true); urlcon.setDoOutput(true); urlcon.setSSLSocketFactory(ssf); urlcon.setRequestMethod(type.toUpperCase()); urlcon.connect(); out = new PrintWriter(urlcon.getOutputStream()); // out.print(params); out.write(params);//相比print,write更直观一点 out.flush();//数据送出 in = urlcon.getInputStream(); BufferedReader buffer = new BufferedReader(new InputStreamReader( in, charset)); StringBuffer bs = new StringBuffer(); String line = null; while ((line = buffer.readLine()) != null) { bs.append(line); } result = bs.toString(); } catch (Exception e) { logger.error(e.getMessage(),e); result = "ERROR:"+e.getMessage(); } logger.info(result); return result; }
2、将公钥证书导入到jdk的信任库中
keytool-import -alias mykeystore -keystore cacerts -files client.cer
cacerts在jdk的\jre\lib\security目录下。
可以通过别名进行查询相关公钥
上面两种方式都可以解决证书的问题。
接下来可能会提示java.security.cert.CertificateException: Nosubject alternative names present
这个错误就是跟上面那个浏览器红色叉叉的原因一样,主机名跟CN不一致,我们可以通过两种办法避免这个错误。
上面两张图可以看到,我使用YU这个主机名访问是没问题的,但是使用IP地址就不行,就是因为我的证书中的CN配置的是YU,
如果写成ip地址的话也是可以的。
1、添加信任,这种方式是不验证这个CN了,绕过去了,直接返回true。
urlcon.setHostnameVerifier(new HostnameVerifier(){
@Override
public boolean verify(String s,SSLSession sslSession) {
return true;
}
});
2、就是修改自己的CN了。
到这个地方SSL单向已经完全弄完。
二、SSL双向
1、创建客户端证书,这里一定设定keyalg 为RSA,这个就是困扰我一天的地方。
keytool -genkeypair-v -alias client -keyalg RSA -storetype PKCS12 -keystore client.p12 -validity 36500
2、导出公钥证书
keytool -export-alias client -keyalg RSA -keystore client.p12 -file servertrustclient.cer
3、将公钥证书导入信任库中,这个信任库是给tomcat用的。配置在truststoreFile。
keytool -import -v-file servertrustclient.cer -keystore servertrustclient.keystore
4、tomcat配置
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
SSLEnabled="true" maxThreads="150" scheme="https" secure="true" acceptCount="100"
clientAuth="true" sslProtocol="TLS" truststoreFile="/etc/starry/starryThc/servertrustclient.keystore"
truststorePass="1112111"
keystoreFile="/etc/starry/starryThc/server.keystore"
keystorePass="123456"/>
5、重启
打开浏览器,我们发现
报错已经变了,这是因为浏览器中没有我们的客户端私钥证书
我们安装刚开始生成的.p12证书。再访问,就可以了。
这个客户端证书我刚开始的时候没有指定加密算法使用什么,keytool的keyalg默认使用了DSA,导致我在chrome、Firefox都一直不能实现双向访问
虽然选择了证书,之后就提示网页无法访问什么的。我前后尝试了好久也没有觉得证书有问题,之所以没有觉得证书有问题,是因为我在ie11中能正常访问,
使用java后台也能正常访问。所以一直认为是浏览器没有将证书带过去。
后来无意中发现前面创建证书是加了RSA的,尝试了一下,发现可以了。是不是跟tomcat也有关系,我没有去尝试,实在是没精力了。
6、双向java后台代码
跟上面的代码基本一样,就是添加了一个客户端证书进去,初始话的时候带上
sslContext.init(keyManagers,trustManagers, new java.security.SecureRandom());
前面是
sslContext.init(null,trustManagers, new java.security.SecureRandom());的写法。
public String doPostsWithOwnCerSSL2(Stringurl,String params,String type,String charset) {
PrintWriter out = null;
InputStream in = null;
String result = null;
try
{
String trustStorePath = "C:\\Users\\Administrator\\Desktop\\学习记录\\keytool\\2018-07-09双向重新配置\\truststore.keystore";
String trustStorePassword = "123456";
// 创建SSLContext对象,并使用我们指定的信任管理器初始化
String privatecert = "C:\\Users\\Administrator\\Desktop\\学习记录\\证书安全问题\\keytool\\2018-07-09双向重新配置\\client.p12";
String privatecertPsd = "111111";
//创建SSL对象
SSLContext sslContext =SSLContext.getInstance("SSL", "SunJSSE");
//重写509TrustManager
//添加信任库,也就是添加服务器的公钥证书进去
TrustManagerFactory trustManagerFactory=TrustManagerFactory.getInstance("SunX509");
KeyStoretrustKeyStore=KeyStore.getInstance("JKS");
trustKeyStore.load(new FileInputStream(trustStorePath),trustStorePassword.toCharArray());
trustManagerFactory.init(trustKeyStore);
TrustManager[] trustManagers=trustManagerFactory.getTrustManagers();
//添加私钥证书,也就是客户端创建的p12证书,公钥已经给到服务器,在tomcat中已经配置
KeyManagerFactory kmf =KeyManagerFactory.getInstance("SunX509","SunJSSE");
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream(privatecert),privatecertPsd.toCharArray());
kmf.init(keyStore,privatecertPsd.toCharArray());
KeyManager[] keyManagers = kmf.getKeyManagers();
sslContext.init(keyManagers,trustManagers, new java.security.SecureRandom());
// 从上述SSLContext对象中得到SSLSocketFactory对象
SSLSocketFactory ssf =sslContext.getSocketFactory();
URL uri = new URL(url);
HttpsURLConnection urlcon =(HttpsURLConnection) uri.openConnection();
urlcon.setDoInput(true);
urlcon.setDoOutput(true);
urlcon.setSSLSocketFactory(ssf);
urlcon.setRequestMethod(type.toUpperCase());
urlcon.setHostnameVerifier(newHostnameVerifier() {
@Override
public boolean verify(Strings, SSLSession sslSession) {
return true;
}
});
urlcon.connect();
out = new PrintWriter(urlcon.getOutputStream());
// out.print(params);
out.write(params);//相比print,write更直观一点
out.flush();//数据送出
in = urlcon.getInputStream();
BufferedReader buffer = new BufferedReader(newInputStreamReader(
in, charset));
StringBuffer bs = new StringBuffer();
String line = null;
while ((line =buffer.readLine()) != null) {
bs.append(line);
}
result = bs.toString();
}
catch (Exception e)
{
logger.error(e.getMessage(),e);
result = "ERROR:"+e.getMessage();
}
logger.info(result);
return result;
}
到此,ssl单双向就完成了,写的比较匆忙,中间写的估计会有点问题,有啥问题,欢迎留言。