作者:骁炉 | 来源:互联网 | 2022-12-01 13:57
有时需要允许不安全的HTTPS连接,例如在一些应该与任何站点一起使用的Web爬行应用程序中.我在旧的HttpsURLConnection API中使用了一个这样的解决方案,最近被JDK 11中的新HttpClient API取代了.使用这个新API允许不安全的HTTPS连接(自签名或过期证书)的方法是什么?
UPD:我尝试的代码(在Kotlin中,但直接映射到Java):
val trustAllCerts = arrayOf(object: X509TrustManager {
override fun getAcceptedIssuers(): Array? = null
override fun checkClientTrusted(certs: Array, authType: String) {}
override fun checkServerTrusted(certs: Array, authType: String) {}
})
val sslCOntext= SSLContext.getInstance("SSL")
sslContext.init(null, trustAllCerts, SecureRandom())
val sslParams = SSLParameters()
// This should prevent host validation
sslParams.endpointIdentificatiOnAlgorithm= ""
httpClient = HttpClient.newBuilder()
.sslContext(sslContext)
.sslParameters(sslParams)
.build()
但在发送时我有异常(尝试使用自签名证书的localhost):
java.io.IOException: No name matching localhost found
使用IP地址而不是localhost会出现"无主题替代名称存在"异常.
在对JDK进行一些调试之后,我发现sslParams
在抛出异常的地方真的被忽略了,并且使用了一些本地创建的实例.进一步调试显示,影响主机名验证算法的唯一方法是将jdk.internal.httpclient.disableHostnameVerification
系统属性设置为true.这似乎是一个解决方案.SSLParameters
在上面的代码中没有任何影响,所以这部分可以被丢弃.使其仅在全局配置看起来像新的HttpClient API中的严重设计缺陷.
1> 小智..:
正如建议的那样,您需要一个SSLContext来忽略错误的证书。在问题中的链接之一中获取SSLContext的确切代码应该可以通过基本创建一个不查看证书的空TrustManager来工作:
private static TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
public static void main (String[] args) throws Exception {
SSLContext sslCOntext= SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new SecureRandom());
HttpClient client = HttpClient.newBuilder()
.sslContext(sslContext)
.build();
上面的问题显然是所有站点完全禁用了服务器身份验证。如果只有一个错误的证书,则可以使用以下命令将其导入密钥库:
keytool -importcert -keystore keystorename -storepass pass -alias cert -file certfile
然后使用InputStream初始化SSLContext,如下所示读取密钥库:
char[] passphrase = ..
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(i, passphrase); // i is an InputStream reading the keystore
KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX");
kmf.init(ks, passphrase);
TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
tmf.init(ks);
sslCOntext= SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
以上两种解决方案均适用于自签名证书。第三种选择是服务器提供有效的,非自签名的证书,但对于与它提供的证书中的任何名称都不匹配的主机,则使用系统属性“ jdk.internal.httpclient.disableHostnameVerification”可以设置为“ true”,这将强制以与以前使用HostnameVerifier API相同的方式来接受证书。请注意,在正常部署中,不希望使用任何这些机制,因为应该可以自动验证任何正确配置的HTTPS服务器提供的证书。
2> Naman..:
同样,对于Java 11,您也可以按照与HttpClient
构建者共享的链接中的选定答案中所述进行类似的工作:
HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofMillis( * 1000))
.sslContext(sc) // SSL context 'sc' initialised as earlier
.sslParameters(parameters) // ssl parameters if overriden
.build();
有样品要求
HttpRequest requestBuilder = HttpRequest.newBuilder()
.uri(URI.create("https://www.example.com/getSomething"))
.GET()
.build();
可以执行为:
httpClient.send(requestBuilder, HttpResponse.BodyHandlers.ofString()); // sends the request
从注释更新,以禁用主机名验证,当前可以使用系统属性:
-Djdk.internal.httpclient.disableHostnameVerification
可以通过程序设置如下:
final Properties props = System.getProperties();
props.setProperty("jdk.internal.httpclient.disableHostnameVerification", Boolean.TRUE.toString());
我找到了解决方案并更新了问题。您的答案大部分是有效的,因此我接受了。但也请注意,必须设置“ jdk.internal.httpclient.disableHostnameVerification”系统属性以摆脱主机名验证异常,不幸的是SSLParameters不会影响此行为,这可能是错误,也可能是一些奇怪的设计决定。