我正在用java为REST API创建一个HTTPS客户端,有些问题我无法以良好的方式解决
需要注意的几点:
-
服务器正在使用自签名证书(在某些环境中,客户可能会使用可信机构对其进行签名(
-
我已经从服务器下载了根证书,并使用
keytool
命令将其添加到truststore
-
我正在将系统属性设置为
System.setProperty("javax.net.ssl.trustStore", path);
-
我可以列出服务器证书来验证它是否正确导入并可用于我的客户端,如:
String filename = path_to_truststore; FileInputStream is = new FileInputStream(filename); KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); String password = "changeit"; keystore.load(is, password.toCharArray()); // This class retrieves the most-trusted CAs from the keystore PKIXParameters params = new PKIXParameters(keystore); // Get the set of trust anchors, which contain the most-trusted CA certificates Iterator it = params.getTrustAnchors().iterator(); while (it.hasNext()) { TrustAnchor ta = (TrustAnchor) it.next(); // Get certificate X509Certificate cert = ta.getTrustedCert(); if (cert.getIssuerDN().getName().contains(server_cert_CN)) { System.out.println(cert.toString()); } }
-
服务器证书在其他使用者名称中没有IP地址,所以我使用自定义
HostnameVerifier
来缓解这个问题,这个问题在中运行良好
之后,我创建了一个URL对象,并获得了如下连接:
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
但我一直在犯错误:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
我试着使用我自己的信任存储。我尝试过从信任库创建自己的SSLSocketFactory,但运气不好。
目前,我能克服上述错误的唯一方法是使用我非常想避免的信任所有证书TrustManager
。
我还尝试使用属性从SSL等获得一些调试级别的日志记录
System.setProperty("javax.net.debug", "ssl,handshake,trustmanager");
但是除了上面的异常之外,我没有得到任何输出。
欢迎任何帮助或想法。我使用的是java 1.8
更新
根据Felix 的请求,我用于创建SSLContext的代码
private SSLContext getSSLContext(File certificatePath) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException {
if (sslContext == null) {
InputStream inputStream = new FileInputStream(certificatePath);
X509Certificate certificate = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(inputStream);
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry(Integer.toString(1), certificate);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, trustManagerFactory.getTrustManagers(), null);
// Setting the context above with create TrustManager does not work.
// An accept all trust manager as below works.
// context.init(null, getSelfSignedTrustManager(), null);
sslContext = context;
}
return sslContext;
}
稍后,我在创建任何连接之前设置上下文,如:
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
您需要将其作为受信任的服务器进行安装。如果它是由不受信任的CA签名的,您也必须安装CA的证书。
根据我评论中GitHub repo中显示的文件,我选择了相关部分并在下面显示。本例使用您为客户端创建并设置的SslContext
。
HttpClient client = HttpClient.newBuilder() // Type java.net.http.HttpClient
.version(HttpClient.Version.HTTP_1_1) // Change to needed HTTP version
.sslContext(getSSLContext(filePath)) // Function from above
.build();
String[] headerArray = {
"Accept", "text/html",
"User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 " +
"Safari/537.36"}; // Extend header if needed
HttpRequest request = HttpRequest.newBuilder() // Type java.net.http.HttpRequest
.uri(uri) // URI of type java.net.URI
.headers(headerArray)
.GET()
.build();
HttpResponse<String> response = null; // Type java.net.http.HttpResponse
try {
response = client.send(request, HttpResponse.BodyHandlers.ofString());
} catch (InterruptedException e) {
e.printStackTrace();
}