Как программно установить SSLContext клиента JAX-WS?
Я работаю на сервере в распределенном приложении, которое имеет браузерные клиенты, а также участвует в общении между сервером и сторонним пользователем.
Мой сервер имеет сертификат с сертификатом CA, позволяющий моим клиентам подключаться с использованием TLS (SSL), используя HTTP/S и XMPP (безопасный). Это все работает нормально.
Теперь мне нужно безопасно подключиться к стороннему серверу с помощью JAX-WS по протоколу HTTPS/SSL. В этом сообщении мой сервер действует как клиент в JAX-WS interation, и у меня есть сертификат клиента, подписанный третьей стороной.
Я попробовал добавить новое хранилище ключей через стандартную конфигурацию системы (-Djavax.net.ssl.keyStore=xyz
), но на мои другие компоненты явно влияет это. Хотя мои другие компоненты используют выделенные параметры для своей конфигурации SSL (my.xmpp.keystore=xxx, my.xmpp.truststore=xxy, ...
), кажется, что они в конечном итоге используют глобальный SSLContext
. (Пространство имен конфигурации my.xmpp.
, казалось, указывало на разделение, но это не так)
Я также попытался добавить свой клиентский сертификат в свое первоначальное хранилище ключей, но, как мне кажется, мне тоже не нравятся другие компоненты.
Я думаю, что мой единственный вариант - это программно подключиться к конфигурации JAX-WS HTTPS, чтобы настроить хранилище ключей и доверительное хранилище для взаимодействия с клиентом JAX-WS.
Любые идеи/указатели на то, как это сделать? Вся информация, которую я нахожу, либо использует метод javax.net.ssl.keyStore
, либо устанавливает глобальный SSLContext
, который, как мне кажется, окажется в том же confilc. Самое близкое, что я получил к чему-то полезному, - это старый отчет об ошибке, который запрашивает нужную мне функцию: Добавить поддержку для передачи SSLContext в среду выполнения клиента JAX-WS
Любое принимает?
Ответы
Ответ 1
Это была жесткая гайка для взлома, поэтому для записи:
Чтобы решить эту проблему, для доступа к разделенному KeyStore
потребовались пользовательские KeyManager
и SSLSocketFactory
, которые используют этот пользовательский KeyManager
.
Я нашел базовый код для этого KeyStore
и SSLFactory
в этой замечательной записи в блоге:
how-to-dynamically-select-a-certificate-alias-when-invoking-web-services
Затем специализированный SSLSocketFactory
необходимо вставить в контекст WebService:
service = getWebServicePort(getWSDLLocation());
BindingProvider bindingProvider = (BindingProvider) service;
bindingProvider.getRequestContext().put("com.sun.xml.internal.ws.transport.https.client.SSLSocketFactory", getCustomSocketFactory());
Где getCustomSocketFactory()
возвращает a SSLSocketFactory
, созданный с использованием метода, упомянутого выше. Это будет работать только для JAX-WS RI из Sun-Oracle, встроенного в JDK, учитывая, что строка, указывающая свойство SSLSocketFactory
, является собственностью этой реализации.
На этом этапе служебная связь JAX-WS защищена через SSL, но если вы загружаете WSDL с того же защищенного сервера(), у вас будет проблема с загрузкой, так как запрос HTTPS для сбора WSDL будет не используйте те же учетные данные, что и веб-служба. Я работал над этой проблемой, создав локальную доступность WSDL (файл:///...) и динамически меняя конечную точку веб-службы: (можно найти хорошее обсуждение того, почему это необходимо, в этом форуме)
bindingProvider.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, webServiceLocation);
Теперь WebService получает загрузочную загрузку и может связываться через SSL с помощью сопоставителя сервера, используя именованный (псевдоним) Client-Certificate и взаимную аутентификацию. ∎
Ответ 2
Вот как я решил это на основе этого сообщения с некоторыми незначительными трюками. Это решение не требует создания каких-либо дополнительных классов.
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() );
Ответ 3
Я пробовал следующее, и он не работал в моей среде:
bindingProvider.getRequestContext().put("com.sun.xml.internal.ws.transport.https.client.SSLSocketFactory", getCustomSocketFactory());
Но другое свойство работало как шарм:
bindingProvider.getRequestContext().put(JAXWSProperties.SSL_SOCKET_FACTORY, getCustomSocketFactory());
Остальная часть кода была взята из первого ответа.
Ответ 4
Объединив ответы Radek и l0co, вы можете получить доступ к WSDL за 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
Ответ 5
Вышеуказанное прекрасно (как я сказал в комментарии), если ваш WSDL не доступен с https://тоже.
Вот мой способ для этого:
Установите SSLSocketFactory по умолчанию:
HttpsURLConnection.setDefaultSSLSocketFactory(...);
Для Apache CXF, который я использую, вам также нужно добавить эти строки в свою конфигурацию:
<http-conf:conduit name="*.http-conduit">
<http-conf:tlsClientParameters useHttpsURLConnectionDefaultSslSocketFactory="true" />
<http-conf:conduit>
Ответ 6
Для тех, кто пытается и до сих пор не работает, это сделало это для меня с Wildfly 8, используя динамический Dispatcher
:
bindingProvider.getRequestContext().put("com.sun.xml.ws.transport.https.client.SSLSocketFactory", yourSslSocketFactory);
Обратите внимание, что часть internal
из ключа Property отсутствует здесь.
Ответ 7
У меня возникли проблемы с доверием к самоподписанному сертификату при настройке диспетчера доверия. Я использовал создатель SSLContexts apache httpclient для создания пользовательского SSLSocketFactory
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);
и передается в new TrustSelfSignedStrategy()
в качестве аргумента в методе loadTrustMaterial
.
Ответ 8
Я попробовал шаги здесь:
http://jyotirbhandari.blogspot.com/2011/09/java-error-invalidalgorithmparameterexc.html
И это исправило проблему. Я сделал некоторые небольшие изменения - я установил два параметра, используя System.getProperty...
Ответ 9
Вы можете переместить свою аутентификацию прокси и персонал ssl в обработчик мыла
port = new SomeService().getServicePort();
Binding binding = ((BindingProvider) port).getBinding();
binding.setHandlerChain(Collections.<Handler>singletonList(new ProxyHandler()));
Это мой пример, все операции в сети
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();
}
}
Он использует UrlConnection, вы можете использовать любую библиотеку, которую вы хотите в обработчике.
Веселиться!
Ответ 10
Следующий подход работал для меня. Этот ответ для реализации JAX-WS Axis2. Я хотел бы построить из maasg ответа. У меня была та же проблема начальной загрузки, о которой упоминал maasg, и я решил ее, сделав файл WSDL локально доступным. Тогда я не смог предоставить контексты SSL, такие как
bindingProvider.getRequestContext().put("com.sun.xml.internal.ws.transport.https.client.SSLSocketFactory", getCustomSocketFactory());
И после того, как много погуглил, я получил ответ из этого блога. Вам нужно добавить Apache httpclient jar в ваш проект.
Protocol authhttps = new Protocol ("https", new AuthSSLProtocolSocketFactory(new URL("file://location_to_your_keystore"), "keystorePassword", new URL("file://location_to_your_truststore"), "trustStorePassword"), 443);
Protocol.registerProtocol("https", authhttps);
Спасибо.