進入正題:
我們系統依賴一個外部查詢接口,是HTTPS提供服務的,由于外部系統沒有測試環境,是以我們都是直接請求的他們生産環境。項目裡面我們使用的HttpClient,其建立SSLClient的代碼如下:
private static CloseableHttpClient createSSLClientDefault() { SSLContext sslContext;
try {
sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
//信任所有
@Override
public boolean isTrusted(X509Certificate[] xcs, String string) {
return true;
}
}).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);
return HttpClients.custom().setSSLSocketFactory(sslsf).build();
} catch (Exception ex) {
logger.error(ex.getMessage(), ex);
}
return HttpClients.createDefault();
}
這樣就可以正常的調用HTTPS服務了。但是當我們的服務部署到線上環境以後,由于生産機器找不到外部接口的域名,是以配置了host,指向該外部接口所在的Nginx伺服器,但是請求一直報錯:
doesn't match any of the subject alternative names: []
。用
curl -k
調用該接口,報
(35) SSL connect error
錯誤,當初還一度懷疑linux機器openssl版本問題,是以強制更新了一個版本還是沒有解決。後面知道curl有個
-v
的參數,錯誤資訊如下:
* Initializing NSS with certpath: sql:/etc/pki/nssdb* CAfile: /etc/pki/tls/certs/ca-bundle.crt
CApath: none
* NSS error -5990
* Closing connection #0
* SSL connect error
curl: (35) SSL connect error
是以按照網上的流程更新了一下nss版本,最終curl -k可以調通服務了。但是通過httpclient還是報錯,是以可以排除掉openssl的問題。是以隻能分析下測試環境和生産環境的差異,看問題到底出現在什麼地方。
後面通過咨詢外部接口的同僚,了解到他們的外部接口服務接入了公司内部的智能網關,在智能網關配置了統一的HTTPS證書。而且隻要接入了智能網關的服務,請求都會先經過智能網關,然後轉發到他們自己的伺服器。但是生産環境和智能網關不在同一網絡,而且解析不到外部接口的域名,隻能通過host映射到他們的Nginx伺服器上。看一下測試環境請求外部接口的網絡拓撲:

測試環境請求網絡拓撲
生産環境的請求網絡拓撲:
生産環境請求網絡拓撲
總算有點眉目了,測試環境能調通主要是能在内部DNS伺服器解析到對應域名。而生産環境是通過host來做的映射,當去解析域名的時候發現目前網絡沒有該域名,是以報錯
doesn't match any of the subject alternative names: []
。當時有兩種思路,第一種:能不能在生産環境開啟通路智能網關的權限,然後直接走域名,但是公司不知道基于什麼考慮,不允許這樣做。第二種:http client是否支援不解析域名的操作,确實http client可以設定不解析host的政策,将其建立SSLClient的代碼調整為如下代碼:
private static CloseableHttpClient createSSLClientDefault() { try {
SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
//不進行主機名驗證
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(builder.build(),
NoopHostnameVerifier.INSTANCE);
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", new PlainConnectionSocketFactory())
.register("https", sslConnectionSocketFactory)
.build();
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
cm.setMaxTotal(100);
CloseableHttpClient httpclient = HttpClients.custom()
.setSSLSocketFactory(sslConnectionSocketFactory)
.setDefaultCookieStore(new BasicCookieStore())
.setConnectionManager(cm).build();
return httpclient;
} catch (Exception e) {
logger.error("createSSLClientDefault error", e);
}
return null;
}
即可解決問題。一個https的小問題确實耗費了我不少的時間,記錄一下。