1、HTTPS介紹
由於HTTP是明文傳輸,會造成安全隱患,所以在一些特定場景中,必須使用HTTPS協議,簡單來說HTTPS=HTTP+SSL/TLS。服務端和客戶端的信息傳輸都是通過TLS進行加密。這樣就能在一定程度上避免敏感信息被截取。
在通信過程中,請求方稱為客戶端,響應方稱為服務端。HTTPS請求流程如圖:
1、客戶端向服務端發送加密版本、加密算法種類、隨機數信息等。
2、服務端返回客戶端發送的信息並帶上服務端證書(公鑰證書)。
3、客戶端效驗服務端證書的合法性。
4、驗證不通過終止通信,驗證通過繼續通信,客戶端將自己所支持的所有加密算法全部發送給服務端供服務端進行選擇。
5、服務端在客戶端發送的加密算法中選擇加密程度最高的機密方式。
6、服務端將選擇的加密算法通過明文返回給客戶端。
7、客戶端收到服務端返回的加密方式后,使用該加密方式生成隨機碼(用作后續通信過程中加密的秘鑰),使用服務端返回的公鑰加密后發送至服務端。
8、服務端收到信息后,使用自己的私鑰進行解密,獲取加密秘鑰。
在之后的通信中,客戶端和服務端都將采用該秘鑰進行加密。
2、自簽證書的生成
生成證書和轉換證書格式需要密碼,秘密建議全部設置一致的。
2.1 生成根證書
# 生成root私鑰
openssl genrsa -out root.key 1024
# 根據私鑰創建根證書請求文件,需要輸入一些證書的元信息:郵箱、域名、密碼等
openssl req -new -out root.csr -key root.key
# 結合私鑰和請求文件,創建根證書,有效期10年
openssl x509 -req -in root.csr -out root.crt -signkey root.key -CAcreateserial -days 3650
2.2 生成服務端證書
# 創建服務端私鑰
openssl genrsa -out server.key 1024
# 根據私鑰生成請求文件需要證書元信息:郵箱、域名、密碼等
openssl req -new -out server.csr -key server.key
# 結合私鑰和請求文件創建服務端證書,有效期10年
openssl x509 -req -in server.csr -out server.crt -signkey server.key -CA root.crt -CAkey root.key -CAcreateserial -days 3650
單向認證不需要生成客戶端證書,所以做單向認證的證書到此就完成了。但是為了之后的測試方便(java中作為服務端使用.p12/.jks類型比較方便,作為客戶端使用.p12/.jks比較方便),將生成的證書進一步轉換。
2.3 證書類型轉換
# 根證書crt轉p12需要密碼
openssl pkcs12 -export -in root.crt -inkey root.key -out root.p12
# 服務端證書crt轉p12需要密碼
openssl pkcs12 -export -in server.crt -inkey server.key -out server.p12
# p12轉jks需要密碼(實際項目中可以不用,僅做測試)
keytool -importkeystore -srckeystore root.p12 -srcstoretype PKCS12 -deststoretype JKS -destkeystore root.jks
生成的全部文件如下:
使用java實現:服務端需要server.p12文件,客戶端需要root.p12文件。可以有其他不同的實現方式,需要不同類型的文件可以通過openssl或者keytool進行證書格式轉化。(jks格式只是用來測試實際項目中可以不生成)。
3、自簽證書的使用
3.1 服務端配置
1、證書放到resources目錄下(其他目錄也可以,配置文件中寫好對應路徑就行)
2、在application中添加配置
# 新增http端口
server.port=8075
# 證書路徑resource下
server.ssl.key-store=classpath:server.p12
# 密碼
server.ssl.key-store-password=hcm@123++
# 服務端限定加密算法
server.ssl.ciphers=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
# 服務端限定TLS版本
server.ssl.protocol=TLSv1.2
根據實際項目中,客戶端和服務端都限定使用這兩種算法。
3、配置之后啟動項目,之前的端口就是https端口了,這樣就不能使用http訪問了,需要使用以下代碼同時支持http。
//配置http
@Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
tomcat.addAdditionalTomcatConnectors(createStandardConnector());
return tomcat;
}
private Connector createStandardConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setPort(“需要使用的http端口”);
return connector;
}
啟動項目出現一個http端口一個https端口就是端口開啟成功了
可以寫個測試方法測試一下
可以看出http和https均可以訪問,至此服務端配置完成並且測試通過。
3.2 客戶端配置
客戶端配置有兩種方案,其一是把根證書導入到jdk中,其二就是本次使用的代碼實現。
由於不太了解導入到jdk是不是有隱患,所有對第一種方案並沒有深入研究,簡單的導入命令如下
# 添加cacerts:
keytool -import -v -trustcacerts -alias 1 -file "C:\Users\zhouyinhang\Desktop\fsdownload\testca1\root.crt" -keystore "G:\Program Files\java\jdk1.8.0_181\jre\lib\security\cacerts"
# 查看cacerts中的證書列表:
keytool -list -keystore "C:\Program Files\Java\jre1.8.0_181\lib\security\cacerts" -storepass changeit
# 刪除cacerts中指定名稱的證書:
keytool -delete -alias 1 -keystore "G:\Program Files\java\jdk1.8.0_181\jre\lib\security\cacerts" -storepass changeit
其中-alias 1 “1”為別名可以通過命令keytool -list -v -keystore root.p12查看,需要注意的是導入jdk的證書格式不可以是p12或者jks格式,可以使用最先生成的crt格式。
下面主要介紹第二種通過代碼實現客戶端根證書的使用。
通過代碼實現主要構造httpClient類
httpClient類作用就是通過記載根證書、密碼、配置TLS版本、配置支持的加密套件、配置是否域名效驗返回一個CloseableHttpClient連接對象,使用該對象可以直接請求https服務端接口,也可以對CloseableHttpClient對象封裝成ReatTemplate。具體實現可以參考源碼。
下面代碼是核心代碼,包括如何建立SSLContext連接,TrustManager如何創建。
其中初始化SSLContext是傳入三個參數分別是keyManagers(授權的秘鑰管理器,用來驗證授權),trustManagers(被授權的證書管理器,用來驗證服務端證書),secureRandom(隨機數,可以填null,SSLContext內部有實現)。項目中cac組件作為客戶端時,而且是單向認證所以只需要傳第二個參數即可。
public static CloseableHttpClient initSSLConfig() throws Exception {
//證書類型
KeyStore keyStore = KeyStore.getInstance("PKCS12");
//加載根證書
keyStore.load(new FileInputStream(PATH),PASSWORD.toCharArray());
// 創建信任庫管理工廠實例
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
// 初始化信任庫
trustManagerFactory.init(keyStore);
TrustManager[] tm = trustManagerFactory.getTrustManagers();
// 建立TLS連接
SSLContext sc = SSLContext.getInstance("TLSv1.2");
// 初始化SSLContext
sc.init(null, tm, null);
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sc,
// 指定TLS版本
new String[]{"TLSv1.2"},
// 指定算法
new String[]{"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384"},
// 取消域名驗證
new HostnameVerifier(){
@Override
public boolean verify(String string, SSLSession ssls) {
return true;
}
}
);
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
return httpClient;
}
使用同一套證書測試結果
測試不同證書時候,有兩種選擇,其一把其他根證書簽發的服務端證書放到請求的服務器上。其二客戶端使用其他的根證書。(根證書和服務器證書不匹配)
錯誤信息如圖
由此可以看出客戶端已經使用根證書效驗服務端返回的證書了。
demo只是作為測試使用,具體使用要結合實際項目。例如cac組件中基線沒有用restTemplate,如果使用restTemplate的話改動太大,只能使用client直接發起請求的方式。
4、正確使用HTTPS
雖然在通信中有必要使用HTTPS,但是不能全部通信都使用,原因主要有兩點:
- HTTPS需要認證,所以相對於HTTP來說效率肯定是比較慢的。
- 要進行HTTPS通信,必須要有證書,這些證書一般都需要向認證機構購買。