在上篇文章中寫到了如何實現服務端程序,主要是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==========