安全でないHTTPS接続を許可する必要がある場合があります。任意のサイトで動作する一部のWebクロールアプリケーションで。 そのようなソリューションの1つ を使用しましたが、最近JDK 11で 新しいHttpClient API に置き換えられた古いHttpsURLConnection APIで、安全でないHTTPS接続を許可する方法(自己署名または有効期限切れの証明書)この新しいAPI
UPD:私が試したコード(Kotlinで、Javaに直接マップ):
val trustAllCerts = arrayOf<TrustManager>(object: X509TrustManager {
override fun getAcceptedIssuers(): Array<X509Certificate>? = null
override fun checkClientTrusted(certs: Array<X509Certificate>, authType: String) {}
override fun checkServerTrusted(certs: Array<X509Certificate>, 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()
しかし、送信時に例外があります(自己署名証明書を使用してローカルホストで試行します):
Java.io.IOException: No name matching localhost found
Localhostの代わりにIPアドレスを使用すると、「サブジェクトの別名がありません」という例外が発生します。
JDKのデバッグ後、例外がスローされ、ローカルで作成されたインスタンスが使用される場所でsslParams
が実際に無視されることがわかりました。さらにデバッグすると、ホスト名検証アルゴリズムに影響を与える唯一の方法はjdk.internal.httpclient.disableHostnameVerification
システムプロパティをtrueに設定します。それが解決策のようです。上記のコードのSSLParameters
は効果がないため、この部分は破棄できます。グローバルにのみ構成可能にすることは、新しいHttpClient APIの重大な設計上の欠陥のように見えます。
Java 11、同様にあなたは同様の努力をすることができます 共有リンクの選択された答えで述べたように として構築されたHttpClient
で:
HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofMillis(<timeoutInSeconds> * 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());
すでに示唆したように、不正な証明書を無視するSSLContextが必要です。質問のリンクの1つでSSLContextを取得する正確なコードは、基本的に証明書を参照しないnull 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();
上記の問題は、すべてのサイトでサーバー認証が完全に無効になっていることです。不正な証明書が1つしかない場合は、次のコマンドでキーストアにインポートできます。
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);
上記のソリューションのいずれかは、自己署名証明書に対して機能します。 3番目のオプションは、サーバーが有効な非自己署名証明書を提供するが、提供する証明書の名前のいずれとも一致しないホストに対して、システムプロパティ「jdk.internal.httpclient.disableHostnameVerification」を提供する場合です。 「true」に設定できます。これにより、HostnameVerifier APIが以前に使用されたのと同じ方法で証明書が強制的に受け入れられます。通常の展開では、これらのメカニズムが使用されることは想定されていないことに注意してください。正しく構成されたHTTPSサーバーによって提供された証明書を自動的に検証できるはずです。