參考文章1(原文鏈接:http://ian.wang/118.htm):
java tomcat 搭建SSL雙向認證以及httpclient代碼

java tomcat 搭建SSL雙向認證以及httpclient代碼 一、生成密鑰庫和證書 可參考以下密鑰生成腳本,根據實際情況做必要的修改,其中需要注意的是:服務端的密鑰庫參數“CN”必須與服務端的IP地址相同,否則會報錯,客戶端的任意。 key.script 1 、生成服務器證書庫 keytool -validity 365 -genkey -v -alias server -keyalg RSA -keystore /opt/web/ssl/server.keystore -dname "CN=localhost,OU=sumscope,O=sumscope,L=Pudong,ST=Shanghai,c=com" -storepass 123456 -keypass 123456 2 、生成客戶端證書庫 keytool -validity 365 -genkeypair -v -alias client -keyalg RSA -storetype PKCS12 -keystore /opt/web/ssl/client.p12 -dname "CN=client,OU=sumscope,O=sumscope,L=Pudong,ST=Shanghai,c=com" -storepass 123456 -keypass 123456 3 、從客戶端證書庫中導出客戶端證書 keytool -export -v -alias client -keystore /opt/web/ssl/client.p12 -storetype PKCS12 -storepass 123456 -rfc -file /opt/web/ssl/client.cer 4 、從服務器證書庫中導出服務器證書 keytool -export -v -alias server -keystore /opt/web/ssl/server.keystore -storepass 123456 -rfc -file /opt/web/ssl/server.cer 5 、生成客戶端信任證書庫(由服務端證書生成的證書庫) keytool -import -v -alias server -file /opt/web/ssl/server.cer -keystore /opt/web/ssl/client.truststore -storepass 123456
其中client.truststore由於是jks格式,需要使用keystore explorer來轉換成bks,然后才可以在android平台使用 6 、將客戶端證書導入到服務器證書庫(使得服務器信任客戶端證書) keytool -import -v -alias client -file /opt/web/ssl/client.cer -keystore /opt/web/ssl/server.keystore -storepass 123456 7 、查看證書庫中的全部證書 keytool -list -keystore /opt/web/ssl/server.keystore -storepass 123456 二、Tomat配置 使用文本編輯器編輯${catalina.base}/conf/server.xml 找到Connector port="8443"的標簽,取消注釋,並修改成如下: <Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" maxThreads="150" scheme="https" secure="true" clientAuth="true" sslProtocol="SSL" keystoreFile="/opt/web/ssl/server.keystore" keystorePass="123456" truststoreFile="/opt/web/ssl/server.keystore" truststorePass="123456" /> 備注: keystoreFile:指定服務器密鑰庫,可以配置成絕對路徑,如“/opt/web/ssl/server.keystore”。 keystorePass:密鑰庫生成時的密碼 truststoreFile:受信任密鑰庫,和密鑰庫相同即可 truststorePass:受信任密鑰庫密碼 三、建立演示項目 項目結構圖: 項目名稱:SSL(隨意) 1. SSLServlet.java package ian.wang.ssl.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.security.cert.X509Certificate; public class SSLServlet extends HttpServlet { private static final String ATTR_CER = "javax.servlet.request.X509Certificate"; private static final String CONTENT_TYPE = "text/plain;charset=UTF-8"; private static final String DEFAULT_ENCODING = "UTF-8"; private static final String SCHEME_HTTPS = "https"; public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType(CONTENT_TYPE); response.setCharacterEncoding(DEFAULT_ENCODING); PrintWriter out = response.getWriter(); out.println("cmd=["+request.getParameter("cmd")+"], data=["+request.getParameter("data")+"]"); X509Certificate[] certs = (X509Certificate[]) request.getAttribute(ATTR_CER); if (certs != null) { int count = certs.length; out.println("共檢測到[" + count + "]個客戶端證書"); for (int i = 0; i < count; i++) { out.println("客戶端證書 [" + (++i) + "]: "); out.println("校驗結果:" + verifyCertificate(certs[--i])); out.println("證書詳細:\r" + certs[i].toString()); } } else { if (SCHEME_HTTPS.equalsIgnoreCase(request.getScheme())) { out.println("這是一個HTTPS請求,但是沒有可用的客戶端證書"); } else { out.println("這不是一個HTTPS請求,因此無法獲得客戶端證書列表 "); } } out.close(); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } private boolean verifyCertificate(X509Certificate certificate) { boolean valid = false; try { certificate.checkValidity(); valid=true; } catch (Exception e) { e.printStackTrace(); } return valid; } } 2. web.xml 說明:該演示項目強制使用了SSL,即普通的HTTP請求也會強制重定向為HTTPS請求,配置在最下面,可以去除,這樣HTTP和HTTPS都可以訪問。 <?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <session-config> <session-timeout>30</session-timeout> </session-config> <servlet> <servlet-name>SSLServlet</servlet-name> <servlet-class>ian.wang.ssl.servlet.SSLServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>SSLServlet</servlet-name> <url-pattern>/sslServlet</url-pattern> </servlet-mapping> <!-- 強制SSL配置,即普通的請求也會重定向為SSL請求 --> <security-constraint> <web-resource-collection> <web-resource-name>SSL</web-resource-name> <url-pattern>/*</url-pattern> <!-- 全站使用SSL --> </web-resource-collection> <user-data-constraint> <description>SSL required</description> <!-- CONFIDENTIAL: 要保證服務器和客戶端之間傳輸的數據不能夠被修改,且不能被第三方查看到 --> <!-- INTEGRAL: 要保證服務器和client之間傳輸的數據不能夠被修改 --> <!-- NONE: 指示容器必須能夠在任一的連接上提供數據。(即用HTTP或HTTPS,由客戶端來決定) --> <transport-guarantee>CONFIDENTIAL</transport-guarantee> </user-data-constraint> </security-constraint> </web-app> 3. index.jsp <%@ page language="java" pageEncoding="UTF-8"%> <!doctype html> <html lang="zh-cn"> <head> <title>客戶端證書上傳</title> <meta http-equiv="pragma" content="no-cache"> <meta http-equiv="cache-control" content="no-cache"> <meta http-equiv="expires" content="0"> </head> <body> <form action="sslServlet" method="post"> <input type="submit" value="提交證書"/> </form> </body> </html> 四、演示及配置 發布演示項目,通過瀏覽器訪問: http://127.0.0.1:8080/SSL 或 https://127.0.0.1:8443/SSL ,提示無法訪問,需要導入客戶端SSL證書: 雙擊“client.p12”或在瀏覽器的工具,輸入生成密鑰時的客戶端密碼“222222”,刷新瀏覽器即可正常訪問了。 五、HttpClient模擬SSL Post請求 1. HttpClientUtil.java package ian.wang.ssl.util; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.security.KeyStore; import java.util.*; public class HttpClientUtil { private static final String KEY_STORE_TYPE_JKS = "jks"; private static final String KEY_STORE_TYPE_P12 = "PKCS12"; private static final String SCHEME_HTTPS = "https"; private static final int HTTPS_PORT = 8443; private static final String HTTPS_URL = "https://localhost:8443/sslServlet"; private static final String KEY_STORE_CLIENT_PATH = "/opt/web/ssl/client.p12"; private static final String KEY_STORE_TRUST_PATH = "/opt/web/ssl/client.truststore"; private static final String KEY_STORE_PASSWORD = "222222"; private static final String KEY_STORE_TRUST_PASSWORD = "222222"; public static void main(String[] args){ String url=HTTPS_URL; Map params=new HashMap(); params.put("cmd","test"); params.put("data","證書1"); String charset="utf-8"; doSSLPost( url, params, charset); } private static void doSSLPost(String url, Map<String, String> map, String charset) { HttpClient httpClient = new DefaultHttpClient(); String result = null; try { KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12); KeyStore trustStore = KeyStore.getInstance(KEY_STORE_TYPE_JKS); InputStream ksIn = new FileInputStream(KEY_STORE_CLIENT_PATH); InputStream tsIn = new FileInputStream(new File(KEY_STORE_TRUST_PATH)); try { keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray()); trustStore.load(tsIn, KEY_STORE_TRUST_PASSWORD.toCharArray()); } finally { try { ksIn.close(); } catch (Exception ignore) { } try { tsIn.close(); } catch (Exception ignore) { } } SSLSocketFactory socketFactory = new SSLSocketFactory(keyStore, KEY_STORE_PASSWORD, trustStore); Scheme sch = new Scheme(SCHEME_HTTPS, HTTPS_PORT, socketFactory); httpClient.getConnectionManager().getSchemeRegistry().register(sch); HttpPost httpPost = new HttpPost(url); //設置參數 if (map != null) { List<NameValuePair> list = new ArrayList<NameValuePair>(); Iterator iterator = map.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, String> elem = (Map.Entry<String, String>) iterator.next(); list.add(new BasicNameValuePair(elem.getKey(), elem.getValue())); } httpPost.setEntity(new UrlEncodedFormEntity(list, charset)); } HttpResponse response = httpClient.execute(httpPost); if (response != null) { HttpEntity resEntity = response.getEntity(); if (resEntity != null) { result = EntityUtils.toString(resEntity, charset); } } System.out.println("result={" + result + "}"); }catch(Exception e){ e.printStackTrace(); } finally { httpClient.getConnectionManager().shutdown(); } } }
六、使用瀏覽器訪問 https應用截圖
1. 打開瀏覽器,訪問測試網址:https://101.231.124.155:8443/ssl , 由於該應用配置了 Tomcat SSL雙向認證,需要客戶端提供證書文件導入成功了,才能正常訪問。在Firefox 瀏覽器中,導入客戶端證書, 在 Firefox 選項 - 高級 - 證書 中, 點擊 查看證書。


2. 點擊“導入”, 選擇客戶端證書文件 client.p12

3. 輸入客戶端證書密碼

4. 客戶端證書驗證成功后,顯示證書如下

5. 打開網址,輸入: https://101.231.124.155:8443 ,瀏覽器會提示 選擇已安裝的證書,點擊“確認”。

6. 顯示如下,則表示SSL證書導入成功, 測試頁面正常顯示了。

本文演示Https雙向驗證實例,Web容器為Tomcat。
一.准備工作:
1.創建服務器KeyStore。
命令:keytool -genkey -alias server_jks_cennavi -keyalg RSA -keypass 123456 -storepass 123456 -keystore server.jks -validity 3650.
keytool命令如下:
-genkey 在用戶主目錄中創建一個默認文件".jks",還會產生一個server_jks_cennavi的別名,server_jks_cennavi中包含用戶的公鑰、私鑰和證書
-alias 產生別名
-keystore 指定密鑰庫的名稱(產生的各類信息將不在.jks文件中
-keyalg 指定密鑰的算法
-validity 指定創建的證書有效期多少天
-keysize 指定密鑰長度
-storepass 指定密鑰庫的密碼
-keypass 指定別名條目的密碼
-dname 指定證書擁有者信息
在相對應的 H:\keys\server 目錄下你能看到一個server.jks文件。
3.導出服務端證書。
定位到服務端 H:\keys\server 目錄:keytool -export -trustcacerts -alias server_jks_cennavi -file server.cer -keystore server.jks -storepass 123456
在相對於的 H:\keys\server 目錄下你能看到一個server.cer文件。
4.創建客戶端KeyStore。
在相對應的 H:\keys\client 目錄下你能看到一個client.p12文件。
5.導出客戶端Cer證書。
定位到服務端 H:\keys\client 目錄:keytool -export -trustcacerts -alias client_p12_cennavi -file client.cer -keystore client.p12 -storepass 123456 -storetype PKCS12
在相對於的 H:\keys\client目錄下你能看到一個client.cer文件。
6.交換導入服務端和客戶端證書,作為雙方信任證書。
將客戶端證書導入服務端keystore:keytool -import -trustcacerts -alias client_p12_cennavi -file client.cer -keystore server.jks
將服務端證書導入客戶端keystore:keytool -import -trustcacerts -alias server_jks_cennavi -file server.cer -keystore client.jks
7.配置服務端tomcat文件下/conf/server.xml。
設置clientAuth="true"為雙向驗證也就是服務端同時驗證客戶可證書。
設置clientAuth="false"為雙向驗證也就是只驗證服務端證書。
8.啟動服務端程序tomcat服務,打開IE輸入訪問地址:https://localhost:8443/ServerDemo,本人的測試程序,程序很簡單里面就一個對外開發的Servlet程序片。
你會發現IE提示:Internet Explore 無法顯示該界面,這是因為我們沒有將客戶端client.p12證書導入到IE中出現的問題。
9.導入客戶端client.p12證書,雙擊client.p12,next-->next-->輸入私鑰密碼。
再次打開IE輸入訪問地址https://localhost:8443/ServerDemo,會提示一個對話框,為什么會出現這個對話框我也不是很清楚....
如圖:
點擊確定:你會看到IE瀏覽器已經阻止了你繼續訪問,原因是沒有安裝服務端證書。
點擊安裝服務端證書。
到這里就告一段落了,那如何用Java程序來調用服務器端對外開放的Servlet程序片?
客戶端代碼:
運行在控制台中可以看到服務端返回的信息。
本人第一次寫文章,寫的不是很好,文章中可能有不正確的地方,請各位朋友指出來,謝謝。
大家有什么好的方法和建議請留言,謝謝。
android使用:
try {
SSLContext context = SSLContext.getInstance("TLS");
if(Constant.isIgnoreC)
{
KeyStore trustStore = KeyStore.getInstance("BKS");
CertificateFactory cerFactory = CertificateFactory.getInstance("X.509");
Certificate cer = cerFactory.generateCertificate(MyApplication.getContextObject().getResources().getAssets().open("bjserver.cer"));bjserver.cer是直接從網址欄中導出的cer證書
trustStore.load(null, null);
trustStore.setCertificateEntry("trust", cer);
// trustStore.load(MyApplication.getContextObject().getResources().getAssets().open("client.truststore.bks"), "123456".toCharArray());//此方法是使用keystore explorer將參考文章1中jks格式的client.truststore轉換成bks格式來設置truststore
//下面注釋掉的5行代碼設置客戶端證書,用於發送給服務端進行校驗(前提是服務端已采用雙向認證,則設置之后就會進行對客戶端證書的校驗)
// KeyStore kks = KeyStore.getInstance("PKCS12", "BC");
// kks.load(MyApplication.getContextObject().getResources().getAssets().open("client.p12"), "123456".toCharArray());
// KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509");
// keyManagerFactory.init(kks, "123456".toCharArray());
// context.init(keyManagerFactory.getKeyManagers(), new TrustManager[] { new EasyX509TrustManager(trustStore) }, null);
//目前不校驗客戶端證書,故init的時候第一個參數傳的是null,只設置了trustmanager為truststore證書,客戶端只信任truststore證書的服務器,即上面代碼中的bjserver.cer所在的服務器
context.init(null, new TrustManager[] { new EasyX509TrustManager(trustStore) }, null);
}
else
context.init(null, new TrustManager[] { new EasyX509TrustManager(null) }, null);
return context;
} catch (Exception e) {
throw new IOException(e.getMessage());
}