说明:本文主要是在平时接口对接开发中遇到的为保证传输安全的情况特要求使用https进行交互的情况下,使用httpClient4.5版本对HTTPS的双向验证的 功能的实现
首先,老生常谈,文章将按照哲学三部曲来解答什么是https,为什么要使用https,httpClient怎么实现https及双向验证。
问题1:什么是https?
https:安全的超文本传输协议;其实质是
加密+认证+完整性保护的HTTP,
也就是说是http的安全版本,https由http进行通信,但利用SSL/TLS协议来加密数据包,并提供对网站服务器的身份认证,保护交换数据的隐私与完整性 。
问题2:为什么要使用https协议?
没有任何一个东西是完美的,总所周知,https出现以前,哪怕是直到现在我们见到更多的还是http协议,那么为什么我们要使用https协议呢?一般来说https是安全的http协议,我们更多的用于支付场景中,https协议主要针对解决http协议以下不足:
1.http使用明文(不加密)通信,内容可能会被窃听
2.不对通信方身份进行认证,可能遭遇伪装攻击
3.无法证明报文的完整性(即准确性),报文可能已被篡改
同样的虽然https解决了HTTP存在的一些问题,但是https的本质是将http的通信接口加入了SSL/TLS协议进行加密处理,加密通信会消耗更多的cup以及内存资源。同样在三次握手事,相对效率会变低,除此之外,要进行https通信,证书是必不可少的。而是用证书必须向认证机构(CA)购买。
问题3:httpClient怎么实现https及双向验证?
对接的服务端发送过来的证书是pfx格式的证书,正常来讲pfx格式证书是包含了公钥/私钥/证书信息等的PKCS12证书,然而直接使用pfx证书作为keyStore却一直报错no trusted certificate error,因此在网上查询了一天,终于把这个问题解决了,解决方案如下:
先将.pfx文件转为.cer文件,再将.cer文件使用keytool工具导入$JAVA_HOME/jre/lib/security/cacerts中,cacerts证书库的默认密码为changeit;
1.pfx文件转为cer文件的方法:
1.使用IE浏览器
2.将.cer文件使用keytool工具导入cacerts证书库
1.keytool -import -alias testCer -keystore cacerts -file C:\Users\admin\Desktop\bcTest.cer
3.java代码中的文件配置
初始化keyStore:为pfx证书路径与密码,策略为PKCS12
初始化trustStore:为cacerts证书库地址,策略为JKS,cacerts证书库默认密码为changeit
Java代码实现:
1 package com.test.sendhttps; 2 3 import java.io.File; 4 import java.io.FileInputStream; 5 import java.io.FileNotFoundException; 6 import java.io.IOException; 7 import java.security.KeyManagementException; 8 import java.security.KeyStore; 9 import java.security.KeyStoreException; 10 import java.security.NoSuchAlgorithmException; 11 import java.security.UnrecoverableKeyException; 12 import java.security.cert.CertificateException; 13 import java.util.Iterator; 14 import java.util.Map; 15 import java.util.Set; 16 import javax.net.ssl.KeyManager; 17 import javax.net.ssl.KeyManagerFactory; 18 import javax.net.ssl.SSLContext; 19 import javax.net.ssl.TrustManager; 20 import javax.net.ssl.TrustManagerFactory; 21 import org.apache.commons.lang3.ObjectUtils; 22 import org.apache.http.Header; 23 import org.apache.http.HttpEntity; 24 import org.apache.http.HttpStatus; 25 import org.apache.http.client.methods.CloseableHttpResponse; 26 import org.apache.http.client.methods.HttpPost; 27 import org.apache.http.config.Registry; 28 import org.apache.http.config.RegistryBuilder; 29 import org.apache.http.conn.socket.ConnectionSocketFactory; 30 import org.apache.http.conn.socket.PlainConnectionSocketFactory; 31 import org.apache.http.conn.ssl.SSLConnectionSocketFactory; 32 import org.apache.http.entity.StringEntity; 33 import org.apache.http.impl.client.CloseableHttpClient; 34 import org.apache.http.impl.client.HttpClients; 35 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; 36 import org.apache.http.util.EntityUtils; 37 import org.apache.log4j.Logger; 38 import com.test.util.JsonUtils; 39 import com.test.util.PropertiesUtil; 40 41 public class SendHttps { 42 private static Logger log = Logger.getLogger(SendHttps.class); 43 static String keyStorePath ; 44 static String keyStorePassword ; 45 static String keyStoreType ; 46 static String trustStorePath ; 47 static String trustStorePassword ; 48 static String trustStoreType; 49 static{ 50 PropertiesUtil proUtil; 51 52 //配置文件读取 53 try { 54 proUtil = new PropertiesUtil(); 55 keyStorePath = proUtil.readValue("sslKeyStorePath"); 56 keyStorePassword = proUtil.readValue("sslKeyStorePassword"); 57 keyStoreType = proUtil.readValue("sslKeyStoreType"); 58 trustStorePath = proUtil.readValue("sslTrustStore"); 59 trustStorePassword = proUtil.readValue("sslTrustStorePassword"); 60 trustStoreType = proUtil.readValue("sslTrustStoreType"); 61 } catch (IOException e) { 62 // TODO Auto-generated catch block 63 log.error("配置文件读取异常"); 64 e.printStackTrace(); 65 } 66 } 67 68 public static String sendToHttps(String reqMsg, String url, Map<String, String> headMap) { 69 log.info("keyFactory"); 70 //初始化KeyManager 71 KeyManagerFactory keyFactory = null; 72 try { 73 keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 74 KeyStore keystore = KeyStore.getInstance(keyStoreType); 75 keystore.load(new FileInputStream(new File(keyStorePath)), null); 76 keyFactory.init(keystore, keyStorePassword.toCharArray()); 77 78 } catch (NoSuchAlgorithmException e) { 79 // TODO Auto-generated catch block 80 e.printStackTrace(); 81 } catch (KeyStoreException e) { 82 // TODO Auto-generated catch block 83 e.printStackTrace(); 84 } catch (CertificateException e) { 85 // TODO Auto-generated catch block 86 e.printStackTrace(); 87 } catch (FileNotFoundException e) { 88 // TODO Auto-generated catch block 89 e.printStackTrace(); 90 } catch (IOException e) { 91 // TODO Auto-generated catch block 92 e.printStackTrace(); 93 } catch (UnrecoverableKeyException e) { 94 // TODO Auto-generated catch block 95 e.printStackTrace(); 96 } 97 KeyManager[] keyManagers = keyFactory.getKeyManagers(); 98 //初始化Trust Manager 99 log.info("keyFactory ="+keyFactory); 100 TrustManagerFactory trustFactory = null; 101 102 try { 103 trustFactory = TrustManagerFactory.getInstance("SunX509"); 104 } catch (NoSuchAlgorithmException e1) { 105 // TODO Auto-generated catch block 106 e1.printStackTrace(); 107 } 108 109 KeyStore tsstore; 110 try { 111 tsstore = KeyStore.getInstance(trustStoreType); 112 tsstore.load(new FileInputStream(new File(trustStorePath)), trustStorePassword.toCharArray()); 113 trustFactory.init(tsstore); 114 log.info("tsstore ="+tsstore+" || trustFactory = "+trustFactory); 115 } catch (KeyStoreException e) { 116 // TODO Auto-generated catch block 117 e.printStackTrace(); 118 } catch (NoSuchAlgorithmException e) { 119 // TODO Auto-generated catch block 120 e.printStackTrace(); 121 } catch (CertificateException e) { 122 // TODO Auto-generated catch block 123 e.printStackTrace(); 124 } catch (FileNotFoundException e) { 125 // TODO Auto-generated catch block 126 e.printStackTrace(); 127 } catch (IOException e) { 128 // TODO Auto-generated catch block 129 e.printStackTrace(); 130 } 131 132 TrustManager[] trustManagers = trustFactory.getTrustManagers(); 133 log.info("trustManagers ="+trustManagers); 134 //注册HtpClient 135 SSLContext sslContext = null; 136 try { 137 sslContext = SSLContext.getInstance("TLS"); 138 sslContext.init(keyManagers, trustManagers, null); 139 } catch (NoSuchAlgorithmException e) { 140 // TODO Auto-generated catch block 141 e.printStackTrace(); 142 } catch (KeyManagementException e) { 143 // TODO Auto-generated catch block 144 e.printStackTrace(); 145 } 146 log.info("sslContext ="+sslContext); 147 148 //设置规则限制 149 SSLConnectionSocketFactory ssf = new SSLConnectionSocketFactory(sslContext, 150 new String[]{"TLSv1","TLSv1.1","TLSv1.2"},null, 151 new HttpsHostnameVerifier()); 152 //注册 153 Registry<ConnectionSocketFactory> socketFactoryRegistry = null; 154 socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create() 155 .register("http", PlainConnectionSocketFactory.INSTANCE) 156 .register("https", ssf).build(); 157 //池化管理 158 PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry); 159 //创建httpClient 160 CloseableHttpClient httpClient; 161 httpClient = HttpClients.custom().setConnectionManager(connManager).build(); 162 163 //设置httpPost 164 HttpPost httpPost = new HttpPost(url); 165 if ((!headMap.isEmpty()) && (headMap.size() > 0)) { 166 Set<String> keys = headMap.keySet(); 167 for (Iterator<String> i = keys.iterator(); i.hasNext(); ) { 168 String key = ObjectUtils.toString(i.next()); 169 if("host".equals(key)){ 170 continue; 171 }else{ 172 log.info("key="+key+",value="+(String)headMap.get(key)); 173 httpPost.addHeader(key, (String)headMap.get(key)); 174 } 175 } 176 } 177 StringEntity reqEntity = new StringEntity(reqMsg, "UTF-8"); 178 179 Header[] types = httpPost.getHeaders("Content-Type"); 180 if ((types == null) || (types.length < 1)) { 181 httpPost.addHeader("Content-Type", "application/json;charset=utf-8"); 182 } 183 184 httpPost.setEntity(reqEntity); 185 CloseableHttpResponse response; 186 try { 187 response = httpClient.execute(httpPost); 188 } catch (Exception e) { 189 e.printStackTrace(); 190 return null; 191 } 192 int statusCode = response.getStatusLine().getStatusCode(); 193 if (statusCode != HttpStatus.SC_OK) { 194 httpPost.abort(); 195 return JsonUtils.setError("Fail to connect . response code = " + statusCode + ". error."); 196 } 197 198 HttpEntity entity = response.getEntity(); 199 String result = null; 200 try { 201 if (entity != null) { 202 result = EntityUtils.toString(entity, "utf-8"); 203 } 204 EntityUtils.consume(entity); 205 response.close(); 206 } catch (Exception e) { 207 log.error("Change charset to utf-8 error."); 208 return JsonUtils.setError("Change charset to utf-8 error."); 209 } 210 return result; 211 } 212 } 213
https设置主机名校验
1 package com.test.sendhttps; 2 3 import javax.net.ssl.HostnameVerifier; 4 import javax.net.ssl.SSLSession; 5 6 public class HttpsHostnameVerifier implements HostnameVerifier { 7 8 /** 9 * 验证对方主机名称 ,防止服务器证书上的Hostname和实际的URL不匹配 10 * 防止链接被重定向到其他的不安全的地址 11 */ 12 @Override 13 public boolean verify(String hostname, SSLSession session) { 14 System.out.println("hostname = [" + hostname + "],session = [" + session.getPeerHost() + "]"); 15 if (hostname.equals("地址") || session.equals("地址")) 16 return true; 17 else 18 return false; 19 } 20 }
配置信息 config.propertities:
sslKeyStorePath=pfx文件路径 sslKeyStorePassword=pfx文件密码 sslKeyStoreType=PKCS12 #jdk中的cacerts库地址 sslTrustStore=C:/Program Files/Java/jre7/lib/security/cacerts #cacerts库密码 sslTrustStorePassword=changeit #cacerts库默认类型 sslTrustStoreType=JKS
后续更换证书操作
查看cacerts证书库中的所有证书
keytool -list -keystore cacerts
删除证书库中别名为testCer的证书
keytool -delete -alias testCer -keystore cacerts
再次导入证书
keytool -import -alias testCer -keystore cacerts -file C:\Users\admin\Desktop\testCer.cer
以上即是所有内容,如有不正确之处,欢迎各位大神批评,斧正,谢谢