ApacheCXF(v3.0.4)を使用してJAX-WSクライアントを実装し、すべてが正常に動作しますが、Java 8( jdk1.8.0_25)。
ログに次の例外が表示されます(-Djavax.net.debug = all):
main, handling exception: Java.net.SocketException: Connection reset
main, SEND TLSv1.2 ALERT: fatal, description = unexpected_message
main, WRITE: TLSv1.2 Alert, length = 2
main, Exception sending alert: Java.net.SocketException: Connection reset by peer: socket write error
Depeer分析の結果、Java 8 with server_name(SNI)が送信されず、Java 7が送信され、 Webサービスの呼び出しは正常に機能します。
Java 8ログ(-Djavax.net.debug = all):「拡張サーバー名」がありません
[...]
Compression Methods: { 0 }
Extension elliptic_curves, curve names: {secp256r1, sect163k1, sect163r2, secp192r1, secp224r1, sect233k1, sect233r1, sect283k1, sect283r1, secp384r1, sect409k1, sect409r1, secp521r1, sect571k1, sect571r1, secp160k1, secp160r1, secp160r2, sect163r1, secp192k1, sect193r1, sect193r2, secp224k1, sect239k1, secp256k1}
Extension ec_point_formats, formats: [uncompressed]
Extension signature_algorithms, signature_algorithms: SHA512withECDSA, SHA512withRSA, SHA384withECDSA, SHA384withRSA, SHA256withECDSA, SHA256withRSA, SHA224withECDSA, SHA224withRSA, SHA1withECDSA, SHA1withRSA, SHA1withDSA, MD5withRSA
***
[...]
Java 7ログ(-Djavax.net.debug = all)(機能):「拡張サーバー名」が設定されている
[...]
Compression Methods: { 0 }
Extension elliptic_curves, curve names: {secp256r1, sect163k1, sect163r2, secp192r1, secp224r1, sect233k1, sect233r1, sect283k1, sect283r1, secp384r1, sect409k1, sect409r1, secp521r1, sect571k1, sect571r1, secp160k1, secp160r1, secp160r2, sect163r1, secp192k1, sect193r1, sect193r2, secp224k1, sect239k1, secp256k1}
Extension ec_point_formats, formats: [uncompressed]
Extension signature_algorithms, signature_algorithms: SHA512withECDSA, SHA512withRSA, SHA384withECDSA, SHA384withRSA, SHA256withECDSA, SHA256withRSA, SHA224withECDSA, SHA224withRSA, SHA1withECDSA, SHA1withRSA, SHA1withDSA, MD5withRSA
Extension server_name, server_name: [Host_name: testeo.hostname.es]
***
[...]
Java 7では、Extension server_name、server_name:[Host_name:testeo.hostname.es]が設定すると、Webサービスの呼び出しが正常に機能します。
Java 8がserver_nameをJava 7に設定したのはなぜですか?Java構成の問題ですか?
前述のように、原因はsetHostnameVerifier()を使用するとSNI(拡張サーバー名)が壊れるJDKバグに関連しています。 https://bugs.openjdk.Java.net/browse/JDK-8144566
回避策:テストの結果、接続のSSLSocketFactoryをデフォルトからほぼすべてに設定すると問題が解決するようです。
これは機能しません:HttpsURLConnection.setSSLSocketFactory((SSLSocketFactory) SSLSocketFactory.getDefault());
これは機能します:HttpsURLConnection.setSSLSocketFactory(new SSLSocketFactoryFacade());
したがって、JAX-WSクライアント用に修正するには、次のようにします。bindingProvider.getRequestContext().put("com.Sun.xml.internal.ws.transport.https.client.SSLSocketFactory", new SSLSocketFactoryFacade());
SSLSocketFactoryファサード:(実際には何もしないことに注意してください)
public class SSLSocketFactoryFacade extends SSLSocketFactory {
SSLSocketFactory sslsf;
public SSLSocketFactoryFacade() {
sslsf = (SSLSocketFactory) SSLSocketFactory.getDefault();;
}
@Override
public String[] getDefaultCipherSuites() {
return sslsf.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return sslsf.getSupportedCipherSuites();
}
@Override
public Socket createSocket(Socket socket, String s, int i, boolean b) throws IOException {
return sslsf.createSocket(socket, s, i, b);
}
@Override
public Socket createSocket(String s, int i) throws IOException, UnknownHostException {
return sslsf.createSocket(s, i);
}
@Override
public Socket createSocket(String s, int i, InetAddress inetAddress, int i1) throws IOException, UnknownHostException {
return sslsf.createSocket(s, i, inetAddress, i1);
}
@Override
public Socket createSocket(InetAddress inetAddress, int i) throws IOException {
return createSocket(inetAddress, i);
}
@Override
public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress1, int i1) throws IOException {
return createSocket(inetAddress, i, inetAddress1, i1);
}
}
あなた、または基礎となるライブラリ(WSライブラリがそれを行う)は、setHostnameVerifier(..)を使用している可能性があります。
Java8にはバグがあり、setHostnameVerifier(..)を使用すると、SNIがクライアント側から実行されません。
この問題が修正されたJDKバージョン8u141以降を使用してください。詳細は JDK 8u141バグ修正 ページを確認してください
Benjamin Parryが提供する解決策を試しましたが、うまくいきませんでした。調べてみたところ、 このソリューション も非常によく似ていますが、SSLSocketFactoryFacadeは、純粋なパススルーではなく、正しいSSLヘッダーを手動で挿入します。わずかに異なる以下の私の最終的なコードを提供しますが、基本的なアイデアのためにjavabreaksでGirish Kamathに与えられたものです:
private static class SSLSocketFactoryFacade extends SSLSocketFactory {
private SSLSocketFactory sslsf;
private SSLParameters sslParameters;
public SSLSocketFactoryFacade(String hostName) {
sslParameters = new SSLParameters();
sslParameters.setServerNames(Arrays.asList(new SNIHostName(hostName)));
sslsf = (SSLSocketFactory) SSLSocketFactory.getDefault();;
}
public Socket createSocket() throws IOException {
Socket socket = sslsf.createSocket();
((SSLSocket) socket).setSSLParameters(sslParameters);
return socket;
}
public Socket createSocket(InetAddress arg0, int arg1, InetAddress arg2, int arg3) throws IOException {
Socket socket = sslsf.createSocket(arg0, arg1, arg2, arg3);
((SSLSocket) socket).setSSLParameters(sslParameters);
return socket;
}
public Socket createSocket(InetAddress arg0, int arg1) throws IOException {
Socket socket = sslsf.createSocket(arg0, arg1);
((SSLSocket) socket).setSSLParameters(sslParameters);
return socket;
}
public Socket createSocket(Socket arg0, InputStream arg1, boolean arg2) throws IOException {
Socket socket = sslsf.createSocket(arg0, arg1, arg2);
((SSLSocket) socket).setSSLParameters(sslParameters);
return socket;
}
public Socket createSocket(Socket arg0, String arg1, int arg2, boolean arg3) throws IOException {
Socket socket = sslsf.createSocket(arg0, arg1, arg2, arg3);
((SSLSocket) socket).setSSLParameters(sslParameters);
return socket;
}
public Socket createSocket(String arg0, int arg1, InetAddress arg2, int arg3)
throws IOException, UnknownHostException {
Socket socket = sslsf.createSocket(arg0, arg1, arg2, arg3);
((SSLSocket) socket).setSSLParameters(sslParameters);
return socket;
}
public Socket createSocket(String arg0, int arg1) throws IOException, UnknownHostException {
Socket socket = sslsf.createSocket(arg0, arg1);
((SSLSocket) socket).setSSLParameters(sslParameters);
return socket;
}
public String[] getDefaultCipherSuites() {
return sslsf.getDefaultCipherSuites();
}
public String[] getSupportedCipherSuites() {
return sslsf.getSupportedCipherSuites();
}
}
そして、私は呼び出すことができます
sslConnection.setSSLSocketFactory(new SSLSocketFactoryFacade(sslConnection.getURL().getHost()));
ここで、sslConnection
はHttpsURLConnection
です。
まず、この「server_name」はSNI(サーバー名表示)拡張に関連付けられています。 Java 8 JSSEドキュメントはそれについて話します ここ 。
ドキュメントには、送信されるサーバー名を設定する方法を示すコード例が含まれています。コードはJava 8.用です。
しかし、私はなぜ(どうやら)Java 7がデフォルトでサーバー名を設定しており、Java 8がそうでない)理由を理解できません。それを理解する方法は、SSLエンジンオブジェクトがどのように作成および初期化されるかを理解するためにデバッガーを使用することです。