J'ai implémenté un client JAX-WS en utilisant ApacheCXF (v3.0.4) et tout fonctionne correctement mais le problème survient lorsque je veux utiliser une connexion sécurisée (SSL/TLS) avec Java 8 ( jdk1.8.0_25).
Je vois l'exception suivante dans le journal (-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
Après une analyse de depeer, j'ai observé que le problème est dû au fait que avec Java 8 le nom_serveur (SNI) n'est pas envoyé mais avec Java 7 il est envoyé et l'invocation du service Web fonctionne correctement.
Journal Java 8 (-Djavax.net.debug = all): "Extension nom_serveur" manquant
[...]
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
***
[...]
Journal Java 7 (-Djavax.net.debug = all) (fonctionne): "Extension nom_serveur" est défini
[...]
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]
***
[...]
On observe qu'avec Java 7 l'extension nom_serveur, nom_serveur: [nom_hôte: testeo.hostname.es] est définir, puis l'appel du service Web fonctionne correctement.
Pourquoi Java 8 n'a pas défini le nom_serveur comme Java 7 l'a fait? S'agit-il d'un problème de configuration Java?)
Comme mentionné, la cause est liée au bogue JDK où l'utilisation de setHostnameVerifier () rompt SNI (Extension server_name). https://bugs.openjdk.Java.net/browse/JDK-8144566
Notre solution de contournement: après avoir testé, nous avons constaté que la définition de SSLSocketFactory d'une connexion à peu près n'importe quoi par défaut semble résoudre le problème.
Cela ne fonctionne pas: HttpsURLConnection.setSSLSocketFactory((SSLSocketFactory) SSLSocketFactory.getDefault());
Cela fonctionne: HttpsURLConnection.setSSLSocketFactory(new SSLSocketFactoryFacade());
Donc, pour le corriger pour un client JAX-WS, vous pouvez faire quelque chose comme ceci: bindingProvider.getRequestContext().put("com.Sun.xml.internal.ws.transport.https.client.SSLSocketFactory", new SSLSocketFactoryFacade());
Notre façade SSLSocketFactory: (Notez que cela ne fait vraiment rien)
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);
}
}
Vous ou les bibliothèques sous-jacentes (la bibliothèque WS le fait) pouvez utiliser setHostnameVerifier (..)
Il y a un bogue dans Java8, où si setHostnameVerifier (..) est utilisé, le SNI ne se fait pas du côté client.
Veuillez utiliser JDK version 8u141 et supérieure où ce problème a été résolu. Veuillez consulter la page Corrections de bugs JDK 8u141 pour plus de détails
J'ai essayé la solution fournie par Benjamin Parry, mais cela n'a pas fonctionné pour moi. Après quelques recherches, j'ai également trouvé cette solution qui semble très similaire, mais SSLSocketFactoryFacade insère manuellement l'en-tête SSL correct au lieu d'être une pure passe-passe. Fournir mon code final ci-dessous qui est légèrement différent, mais crédit à donner à Girish Kamath chez javabreaks pour l'idée de base:
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();
}
}
Et puis je peux appeler
sslConnection.setSSLSocketFactory(new SSLSocketFactoryFacade(sslConnection.getURL().getHost()));
où sslConnection
est le HttpsURLConnection
.
Tout d'abord, ce truc "nom_serveur" est associé à l'extension SNI (Server Name Indication). La Java 8 JSSE en parle ici .
La documentation comprend un exemple de code qui montre comment définir les noms de serveur qui sont envoyés. Le code est pour Java 8.
Cependant, je ne peux pas comprendre pourquoi (apparemment) Java 7 définit le nom du serveur par défaut, et Java 8 ne l'est pas.) moyen de le comprendre serait d'utiliser un débogueur pour comprendre comment l'objet moteur SSL est créé et initialisé.)