在上篇文章中写到了如何实现服务端程序,主要是netty实现的。还有如何生成证书和密钥库。
这篇文章主要讲客户端如何实现:
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通信。
运行结果:
服务器端
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Your session is protected by TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA cipher suite.
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
七月 10, 2014 11:55:07 上午 https.HttpDemoServerHandler exceptionCaught
警告:
java.io.IOException: 远程主机强迫关闭了一个现有的连接。
at sun.nio.ch.SocketDispatcher.read0(Native Method)
at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:43)
at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:223)
at sun.nio.ch.IOUtil.read(IOUtil.java:192)
at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:375)
at io.netty.buffer.UnpooledUnsafeDirectByteBuf.setBytes(UnpooledUnsafeDirectByteBuf.java:446)
at io.netty.buffer.AbstractByteBuf.writeBytes(AbstractByteBuf.java:871)
at io.netty.channel.socket.nio.NioSocketChannel.doReadBytes(NioSocketChannel.java:225)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:115)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:507)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:464)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:378)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:350)
at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:116)
at java.lang.Thread.run(Thread.java:745)
有异常,这不是主要的,是因为没有关闭连接。
客户端
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==========