在上篇文章中写到了如何实现服务端程序,主要是netty实现的。还有如何生成证书和密钥库。java
这篇文章主要讲客户端如何实现:apache
HttpClientUtils2.java安全
package https; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.RequestBuilder; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.conn.routing.HttpRoute; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.PlainConnectionSocketFactory; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.SSLContexts; import org.apache.http.conn.ssl.TrustSelfSignedStrategy; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import javax.net.ssl.SSLContext; import java.io.File; import java.io.FileInputStream; import java.security.KeyStore; import java.security.SecureRandom; import java.util.*; /** * ssl通讯的client */ public class HttpClientUtils2 { private static PoolingHttpClientConnectionManager secureConnectionManager; private static HttpClientBuilder secureHttpBulder = null; private static RequestConfig requestConfig = null; private static int MAXCONNECTION = 10; private static int DEFAULTMAXCONNECTION = 5; private static String CLIENT_KEY_STORE = "E:\\https\\client.keystore"; private static String CLIENT_TRUST_KEY_STORE = "E:\\https\\client.truststore"; private static String CLIENT_KEY_STORE_PASSWORD = "123456"; private static String CLIENT_TRUST_KEY_STORE_PASSWORD = "123456"; private static String CLIENT_KEY_PASS = "123456"; /** * 进行安全通讯的主机和端口 */ private static String HOST = "127.0.0.1"; private static int PORT = 8888; static { //设置http的状态参数 requestConfig = RequestConfig.custom() .setSocketTimeout(5000) .setConnectTimeout(5000) .setConnectionRequestTimeout(5000) .build(); try { KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); FileInputStream trustStoreInput = new FileInputStream(new File(CLIENT_TRUST_KEY_STORE)); trustStore.load(trustStoreInput, CLIENT_TRUST_KEY_STORE_PASSWORD.toCharArray()); KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); FileInputStream clientKeyStoreInput = new FileInputStream(new File(CLIENT_KEY_STORE)); clientKeyStore.load(clientKeyStoreInput, CLIENT_KEY_STORE_PASSWORD.toCharArray()); SSLContext sslContext = SSLContexts.custom() .loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()) .loadKeyMaterial(clientKeyStore, CLIENT_KEY_PASS.toCharArray()) .setSecureRandom(new SecureRandom()) .useSSL() .build(); ConnectionSocketFactory plainSocketFactory = new PlainConnectionSocketFactory(); SSLConnectionSocketFactory sslSocketFactoy = new SSLConnectionSocketFactory( sslContext, new String[]{"SSLv3"}, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); Registry<ConnectionSocketFactory> r = RegistryBuilder.<ConnectionSocketFactory>create() .register("http", plainSocketFactory) .register("https", sslSocketFactoy) .build(); secureConnectionManager = new PoolingHttpClientConnectionManager(r); HttpHost target = new HttpHost(HOST, PORT, "https"); secureConnectionManager.setMaxTotal(MAXCONNECTION); //设置每一个Route的链接最大数 secureConnectionManager.setDefaultMaxPerRoute(DEFAULTMAXCONNECTION); //设置指定域的链接最大数 secureConnectionManager.setMaxPerRoute(new HttpRoute(target), 20); secureHttpBulder = HttpClients.custom().setConnectionManager(secureConnectionManager); } catch (Exception e) { throw new Error("Failed to initialize the server-side SSLContext", e); } } public static CloseableHttpClient getSecureConnection() throws Exception { return secureHttpBulder.build(); } public static HttpUriRequest getRequestMethod(Map<String, String> map, String url, String method) { List<NameValuePair> params = new ArrayList<NameValuePair>(); Set<Map.Entry<String, String>> entrySet = map.entrySet(); for (Map.Entry<String, String> e : entrySet) { String name = e.getKey(); String value = e.getValue(); NameValuePair pair = new BasicNameValuePair(name, value); params.add(pair); } HttpUriRequest reqMethod = null; if ("post".equals(method)) { reqMethod = RequestBuilder.post().setUri(url) .addParameters(params.toArray(new BasicNameValuePair[params.size()])) .setConfig(requestConfig).build(); } else if ("get".equals(method)) { reqMethod = RequestBuilder.get().setUri(url) .addParameters(params.toArray(new BasicNameValuePair[params.size()])) .setConfig(requestConfig).build(); } return reqMethod; } public static void main(String args[]) throws Exception { Map<String, String> map = new HashMap<String, String>(); map.put("account", "sdsdsd"); map.put("password", "98765"); HttpClient client = getSecureConnection(); //使用ssl通讯 HttpUriRequest post = getRequestMethod(map, "https://127.0.0.1:8888/", "post"); HttpResponse response = client.execute(post); if (response.getStatusLine().getStatusCode() == 200) { HttpEntity entity = response.getEntity(); String message = EntityUtils.toString(entity, "utf-8"); System.out.println(message); } else { System.out.println("请求失败"); } } }
上面的httpclient实现了链接池,并能够进行ssl双向认证的通讯过程。其实也能够进行不加密的http通讯。服务器
运行结果:session
服务器端app
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>dom
Your session is protected by TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA cipher suite.socket
VERSION: HTTP/1.1ide
REQUEST_URI: /post
HEADER: Content-Type=application/x-www-form-urlencoded; charset=ISO-8859-1
HEADER: Host=127.0.0.1:8888
HEADER: Connection=Keep-Alive
HEADER: User-Agent=Apache-HttpClient/4.3.3 (java 1.5)
HEADER: Accept-Encoding=gzip,deflate
HEADER: Content-Length=29
七月 10, 2014 11:55:07 上午 https.HttpDemoServerHandler exceptionCaught
警告:
java.io.IOException: 远程主机强迫关闭了一个现有的链接。
有异常,这不是主要的,是由于没有关闭链接。
客户端
WELCOME TO THE WILD WILD WEB SERVER
===================================
VERSION: HTTP/1.1
REQUEST_URI: /
HEADER: Content-Type=application/x-www-form-urlencoded; charset=ISO-8859-1
HEADER: Host=127.0.0.1:8888
HEADER: Connection=Keep-Alive
HEADER: User-Agent=Apache-HttpClient/4.3.3 (java 1.5)
HEADER: Accept-Encoding=gzip,deflate
HEADER: Content-Length=29
Is Chunked: false
IsMultipart: false
BODY Attribute: Attribute:Mixed: password=98765
BODY Attribute: Attribute:Mixed: account=sdsdsd
END OF POST CONTENT
Process finished with exit code 0
httpclient还有一种方式能够进行ssl通讯。下面看这段代码:
ClientCustomSSL.java
package https; import java.io.File; import java.io.FileInputStream; import java.security.KeyStore; import javax.net.ssl.SSLContext; import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.conn.ssl.SSLContexts; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.TrustSelfSignedStrategy; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; /** * This example demonstrates how to create secure connections with a custom SSL * context. */ public class ClientCustomSSL { private static String CLIENT_KEY_STORE = "E:\\https\\client.keystore"; private static String CLIENT_TRUST_KEY_STORE = "E:\\https\\client.truststore"; private static String CLIENT_KEY_STORE_PASSWORD = "123456"; private static String CLIENT_TRUST_KEY_STORE_PASSWORD = "123456"; private static String CLIENT_KEY_PASS = "123456"; public final static void main(String[] args) throws Exception { KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); FileInputStream instream = new FileInputStream(new File(CLIENT_TRUST_KEY_STORE)); try { trustStore.load(instream, CLIENT_TRUST_KEY_STORE_PASSWORD.toCharArray()); } finally { instream.close(); } KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); FileInputStream keyStoreInput = new FileInputStream(new File(CLIENT_KEY_STORE)); try { keyStore.load(keyStoreInput, CLIENT_KEY_STORE_PASSWORD.toCharArray()); } finally { keyStoreInput.close(); } // Trust own CA and all self-signed certs SSLContext sslcontext = SSLContexts.custom() .loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()) .loadKeyMaterial(keyStore, CLIENT_KEY_PASS.toCharArray()) .build(); // Allow TLSv1 protocol only SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( sslcontext, new String[]{"SSLv3"}, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); CloseableHttpClient httpclient = HttpClients.custom() .setSSLSocketFactory(sslsf) .build(); try { HttpPost httpPost = new HttpPost("https://127.0.0.1:8888/"); System.out.println("executing request" + httpPost.getRequestLine()); CloseableHttpResponse response = httpclient.execute(httpPost); try { HttpEntity entity = response.getEntity(); System.out.println("----------------------------------------"); System.out.println(response.getStatusLine()); if (entity != null) { System.out.println("Response content length: " + entity.getContentLength()); } EntityUtils.consume(entity); } finally { response.close(); } } finally { httpclient.close(); } } }
上面这段代码没有用到链接池,比较简单的实现了双向认证的ssl通讯过程。
运行结果:
----------------------------------------
HTTP/1.1 200 OK
Response content length: -1
Process finished with exit code 0
==========END==========