Je travaille sur un serveur dans une application distribuée avec des clients de navigation et participe également à la communication de serveur à serveur avec une tierce partie . Mon serveur possède un certificat signé par une autorité de certification permettant à mes clients de se connecter à l'aide de ) communication via HTTP/S et XMPP (sécurisé). Tout fonctionne bien.
Maintenant, je dois me connecter en toute sécurité à un serveur tiers à l'aide de JAX-WS via HTTPS/SSL. Dans cette communication, mon serveur agit en tant que client dans l'interaction JAX-WS et j'ai un certificat client signé par le tiers.
J'ai essayé d'ajouter un nouveau magasin de clés via la configuration système standard (-Djavax.net.ssl.keyStore=xyz
), mais mes autres composants en sont clairement affectés. Bien que mes autres composants utilisent des paramètres dédiés pour leur configuration SSL (my.xmpp.keystore=xxx, my.xmpp.truststore=xxy, ...
), il semble qu’ils finissent par utiliser la variable globale SSLContext
. (L'espace de noms de configuration my.xmpp.
semblait indiquer une séparation, mais ce n'est pas le cas)
J'ai également essayé d'ajouter mon certificat client dans mon magasin de clés d'origine, mais, encore une fois, mes autres composants ne semblent pas l'apprécier non plus.
Je pense qu'il ne me reste plus qu'à connecter par programme la configuration HTTPS JAX-WS pour configurer le fichier de clés et le fichier de clés certifiées pour l'interaction client JAX-WS.
Des idées/indications sur comment faire cela? Toutes les informations que je trouve utilisent la méthode javax.net.ssl.keyStore
ou définissent la variable SSLContext
globale qui, je suppose, aboutira à la même configuration. Ce qui était le plus proche de quelque chose d’utile était cet ancien rapport de bogue qui demandait la fonctionnalité dont j'avais besoin: Ajout de la prise en charge pour la transmission d’un contexte SSLC au contexte d’exécution du client JAX-WS
Tout prend?
Celui-ci était un dur à casser, alors pour l'enregistrement:
Pour résoudre ce problème, il fallait une KeyManager
personnalisée et un SSLSocketFactory
qui utilise cette coutume KeyManager
pour accéder à la KeyStore
..__ séparée. J'ai trouvé le code de base de cette KeyStore
et SSLFactory
sur cette excellente entrée de blog: comment sélectionner dynamiquement un alias de certificat lors de l'appel de services Web
Ensuite, le fichier SSLSocketFactory
spécialisé doit être inséré dans le contexte WebService:
service = getWebServicePort(getWSDLLocation());
BindingProvider bindingProvider = (BindingProvider) service;
bindingProvider.getRequestContext().put("com.Sun.xml.internal.ws.transport.https.client.SSLSocketFactory", getCustomSocketFactory());
Où getCustomSocketFactory()
renvoie un SSLSocketFactory
créé à l'aide de la méthode mentionnée ci-dessus. Cela ne fonctionnerait que pour JAX-WS RI à partir de l'implémentation Sun-Oracle intégrée dans le JDK, étant donné que la chaîne indiquant la propriété SSLSocketFactory
est la propriété de cette implémentation.
A ce stade, la communication du service JAX-WS est sécurisée via SSL. Toutefois, si vous chargez le fichier WSDL à partir du même serveur sécurisé (), vous rencontrez un problème d'amorçage, car la demande HTTPS visant à rassembler le fichier WSDL ne sera pas utilisée. les mêmes informations d'identification que le service Web. J'ai résolu ce problème en rendant le WSDL disponible localement (fichier: /// ...) et en modifiant de manière dynamique le point de terminaison du service Web: (une bonne discussion sur les raisons de cette nécessité peut être trouvée dans ce forum )
bindingProvider.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, webServiceLocation);
Maintenant, le WebService est démarré et peut communiquer via SSL avec le serveur correspondant à l'aide d'un certificat client (alias) nommé et d'une authentification mutuelle. ∎
Voici comment je l'ai résolu en me basant sur cet article avec quelques modifications mineures. Cette solution ne nécessite pas la création de classes supplémentaires.
SSLContext sc = SSLContext.getInstance("SSLv3");
KeyManagerFactory kmf =
KeyManagerFactory.getInstance( KeyManagerFactory.getDefaultAlgorithm() );
KeyStore ks = KeyStore.getInstance( KeyStore.getDefaultType() );
ks.load(new FileInputStream( certPath ), certPasswd.toCharArray() );
kmf.init( ks, certPasswd.toCharArray() );
sc.init( kmf.getKeyManagers(), null, null );
((BindingProvider) webservicePort).getRequestContext()
.put(
"com.Sun.xml.internal.ws.transport.https.client.SSLSocketFactory",
sc.getSocketFactory() );
J'ai essayé ce qui suit et cela n'a pas fonctionné sur mon environnement:
bindingProvider.getRequestContext().put("com.Sun.xml.internal.ws.transport.https.client.SSLSocketFactory", getCustomSocketFactory());
Mais différentes propriétés ont fonctionné comme un charme:
bindingProvider.getRequestContext().put(JAXWSProperties.SSL_SOCKET_FACTORY, getCustomSocketFactory());
Le reste du code provient de la première réponse.
En combinant les réponses de Radek et de l0co, vous pouvez accéder au WSDL derrière https:
SSLContext sc = SSLContext.getInstance("TLS");
KeyManagerFactory kmf = KeyManagerFactory
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(getClass().getResourceAsStream(keystore),
password.toCharArray());
kmf.init(ks, password.toCharArray());
sc.init(kmf.getKeyManagers(), null, null);
HttpsURLConnection
.setDefaultSSLSocketFactory(sc.getSocketFactory());
yourService = new YourService(url); //Handshake should succeed
Ce qui précède convient (comme je l’ai dit dans un commentaire) à moins que votre WSDL ne soit accessible avec https: //.
Voici ma solution de contournement pour ceci:
Définissez SSLSocketFactory par défaut:
HttpsURLConnection.setDefaultSSLSocketFactory(...);
Pour Apache CXF que j’utilise, vous devez également ajouter ces lignes à votre configuration:
<http-conf:conduit name="*.http-conduit">
<http-conf:tlsClientParameters useHttpsURLConnectionDefaultSslSocketFactory="true" />
<http-conf:conduit>
J'ai essayé les étapes ici:
http://jyotirbhandari.blogspot.com/2011/09/Java-error-invalvalgorithmparameterexc.html
Et cela a résolu le problème. J'ai fait quelques ajustements mineurs - J'ai défini les deux paramètres à l'aide de System.getProperty ...
Vous pouvez déplacer votre personnel d'authentification proxy et SSL vers un gestionnaire de savon
port = new SomeService().getServicePort();
Binding binding = ((BindingProvider) port).getBinding();
binding.setHandlerChain(Collections.<Handler>singletonList(new ProxyHandler()));
Ceci est mon exemple, faites toutes les opérations réseau
class ProxyHandler implements SOAPHandler<SOAPMessageContext> {
static class TrustAllHost implements HostnameVerifier {
public boolean verify(String urlHostName, SSLSession session) {
return true;
}
}
static class TrustAllCert implements 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) {
}
}
private SSLSocketFactory socketFactory;
public SSLSocketFactory getSocketFactory() throws Exception {
// just an example
if (socketFactory == null) {
SSLContext sc = SSLContext.getInstance("SSL");
TrustManager[] trustAllCerts = new TrustManager[] { new TrustAllCert() };
sc.init(null, trustAllCerts, new Java.security.SecureRandom());
socketFactory = sc.getSocketFactory();
}
return socketFactory;
}
@Override public boolean handleMessage(SOAPMessageContext msgCtx) {
if (!Boolean.TRUE.equals(msgCtx.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY)))
return true;
HttpURLConnection http = null;
try {
SOAPMessage outMessage = msgCtx.getMessage();
outMessage.setProperty(SOAPMessage.CHARACTER_SET_ENCODING, "UTF-8");
// outMessage.setProperty(SOAPMessage.WRITE_XML_DECLARATION, true); // Not working. WTF?
ByteArrayOutputStream message = new ByteArrayOutputStream(2048);
message.write("<?xml version='1.0' encoding='UTF-8'?>".getBytes("UTF-8"));
outMessage.writeTo(message);
String endpoint = (String) msgCtx.get(BindingProvider.ENDPOINT_ADDRESS_PROPERTY);
URL service = new URL(endpoint);
Proxy proxy = Proxy.NO_PROXY;
//Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("{proxy.url}", {proxy.port}));
http = (HttpURLConnection) service.openConnection(proxy);
http.setReadTimeout(60000); // set your timeout
http.setConnectTimeout(5000);
http.setUseCaches(false);
http.setDoInput(true);
http.setDoOutput(true);
http.setRequestMethod("POST");
http.setInstanceFollowRedirects(false);
if (http instanceof HttpsURLConnection) {
HttpsURLConnection https = (HttpsURLConnection) http;
https.setHostnameVerifier(new TrustAllHost());
https.setSSLSocketFactory(getSocketFactory());
}
http.setRequestProperty("Content-Type", "application/soap+xml; charset=utf-8");
http.setRequestProperty("Content-Length", Integer.toString(message.size()));
http.setRequestProperty("SOAPAction", "");
http.setRequestProperty("Host", service.getHost());
//http.setRequestProperty("Proxy-Authorization", "Basic {proxy_auth}");
InputStream in = null;
OutputStream out = null;
try {
out = http.getOutputStream();
message.writeTo(out);
} finally {
if (out != null) {
out.flush();
out.close();
}
}
int responseCode = http.getResponseCode();
MimeHeaders responseHeaders = new MimeHeaders();
message.reset();
try {
in = http.getInputStream();
IOUtils.copy(in, message);
} catch (final IOException e) {
try {
in = http.getErrorStream();
IOUtils.copy(in, message);
} catch (IOException e1) {
throw new RuntimeException("Unable to read error body", e);
}
} finally {
if (in != null)
in.close();
}
for (Map.Entry<String, List<String>> header : http.getHeaderFields().entrySet()) {
String name = header.getKey();
if (name != null)
for (String value : header.getValue())
responseHeaders.addHeader(name, value);
}
SOAPMessage inMessage = MessageFactory.newInstance()
.createMessage(responseHeaders, new ByteArrayInputStream(message.toByteArray()));
if (inMessage == null)
throw new RuntimeException("Unable to read server response code " + responseCode);
msgCtx.setMessage(inMessage);
return false;
} catch (Exception e) {
throw new RuntimeException("Proxy error", e);
} finally {
if (http != null)
http.disconnect();
}
}
@Override public boolean handleFault(SOAPMessageContext context) {
return false;
}
@Override public void close(MessageContext context) {
}
@Override public Set<QName> getHeaders() {
return Collections.emptySet();
}
}
Il utilise UrlConnection, vous pouvez utiliser n’importe quelle bibliothèque que vous voulez dans le gestionnaire . Amusez-vous!
J'ai eu des problèmes pour faire confiance à un certificat auto-signé lors de la configuration du gestionnaire de confiance. J'ai utilisé le générateur SSLContexts de Apache httpclient pour créer une variable SSLSocketFactory
personnalisée.
SSLContext sslcontext = SSLContexts.custom()
.loadKeyMaterial(keyStoreFile, "keystorePassword.toCharArray(), keyPassword.toCharArray())
.loadTrustMaterial(trustStoreFile, "password".toCharArray(), new TrustSelfSignedStrategy())
.build();
SSLSocketFactory customSslFactory = sslcontext.getSocketFactory()
bindingProvider.getRequestContext().put(JAXWSProperties.SSL_SOCKET_FACTORY, customSslFactory);
et en passant la new TrustSelfSignedStrategy()
en tant qu'argument dans la méthode loadTrustMaterial
.