java SSLContext創(chuàng)建方式
概述
HTTPS相對于HTTP多了SSL(security sock layer),應用層將數據丟給TCP時,需要經過SSL層的加密處理;TCP層的數據丟給應用層時,需要經過SSL層的解密處理。
因此網絡中傳輸的都是加密后的數據,提高的通信的安全性。

HTTPS除了擁有傳輸加密的功能,還提供身份認證,防止對端偽造身份。這個是通過HTTPS證書實現(xiàn)。
注下圖中對HTTPS握手過程進行簡化,握手過程的關鍵步驟就是證書校驗及對稱加密秘鑰的協(xié)商,另外客戶端證書的校驗是可選的。

問題
標準證書都是第三方機構頒發(fā)的,需要付費找第三方機構申請。有些出于節(jié)省成本或者省事的目的,一般會使用工具生成自簽名的證書。
瀏覽器訪問這種自簽名證書的網址時就會彈出不安全的提示,如果是java代碼訪問這種網址時就會拋出異常。
解決這種問題有2個途徑,一種是忽略證書認證,另一種就是將自簽名的證書添加到受信證書庫中。
SSLContext關鍵參數說明
SSLContext都是通過SSLContext.getInstance(String protocol)方法獲取,獲取后需要調用init方法初始化。
init方法簽名如下:
public final void init(KeyManager[] km, TrustManager[] tm,SecureRandom random)
KeyManager
- 用于HTTPS雙向認證時,客戶端向服務端發(fā)送的認證信息(證書);與不同的服務端交互時,客戶端可能用不同的身份(證書),所以需要KeyManager進行管理。
- 如果只是單向認證,KeyManager[]可以為空。
TrustManager
- 用于客戶端檢測服務端發(fā)送過來的證書。
KeyStore:只是單純的存儲證書、秘鑰數據,不包含任何邏輯。
TrustManager,KeyManager可以使用KeyStore得到證書列表進行相應的業(yè)務處理。
忽略證書認證
有些語言比如Python是可以修改全局配置去忽略證書檢查,但是Java好像沒有提供這種功能,所以需要覆寫證書認證的類X509TrustManager 。
忽略證書認證后,雙方通信的時候仍然是加密的,只是無法對服務端的身份進行認證,有服務端偽造的風險。如果都是在內網,可以采用這種簡潔的手法。
public SSLContext buildSSLContext() throws Exception {
X509TrustManager x509TrustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init((KeyManager[]) null, new X509TrustManager[] {x509TrustManager}, (SecureRandom) null);
return sslContext;
}
將自簽名證書放到受信證書庫
將證書直接導入到JDK的證書庫
待續(xù)
通過編碼的方式將證書放到受信證書列表中
@Override
public SSLContext buildSSLContext() throws Exception {
KeyStore keyStore = buildKeyStore();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init((KeyManager[]) null, tmf.getTrustManagers(), (SecureRandom) null);
return sslContext;
}
private KeyStore buildKeyStore() throws Exception {
// 獲取自簽名的證書
List<X509Certificate> certs = loadHttpsCerts();
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
// 獲得jdk證書庫中的證書
Arrays.stream(trustManagerFactory.getTrustManagers())
.filter(manager -> manager instanceof X509TrustManager)
.findFirst()
.ifPresent(manager -> {
List<X509Certificate> jdkCerts = Arrays.asList(((X509TrustManager) manager).getAcceptedIssuers());
certs.addAll(jdkCerts);
});
certs.stream().forEach(cert -> {
keyStore.setCertificateEntry(cert.getSubjectDN().getName(), cert);
});
return keyStore;
}
private List<X509Certificate> loadHttpsCerts() throws IOException, CertificateException {
List<X509Certificate> certs = new ArrayList<>(32);
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources(HTTPS_CERTS_PATH);
for (Resource resource : resources) {
InputStream inStream = resource.getInputStream();
CertificateFactory factory = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) factory.generateCertificate(inStream);
certs.add(cert);
}
return certs;
}
總結
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

