我正在使用具有浏览器客户端的分布式应用程序中的服务器,并且还参与与第三方的服务器到服务器通信。 我的服务器有一个CA签名证书,让我的客户端使用HTTP / S和XMPP(安全)使用TLS(SSL)通信进行连接。这一切都很好。
现在我需要通过HTTPS / SSL使用JAX-WS安全地连接到第三方服务器。在此通信中,我的服务器在JAX-WS交互中充当客户端,并且我已经由第三方签署了客户端证书。
我尝试通过标准系统配置(-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中。最接近我得到帮助的是这个旧的错误报告请求我需要的功能:Add support for passing an SSLContext to the JAX-WS client runtime
任何需要?
答案 0 :(得分:54)
这个很难破解,所以记录:
要解决此问题,需要使用此自定义KeyManager
的自定义SSLSocketFactory
和KeyManager
来访问已分隔的KeyStore
。
我在这篇优秀的博客文章中找到了此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()
返回使用上述方法创建的SSLSocketFactory
。这只适用于内置于JDK中的Sun-Oracle impl的JAX-WS RI,因为指示SSLSocketFactory
属性的字符串对于此实现是专有的。
在此阶段,JAX-WS服务通信通过SSL加以保护,但如果您从同一个安全服务器()加载WSDL,那么您将遇到引导问题,因为收集WSDL的HTTPS请求将不使用与Web服务相同的凭据。我通过使WSDL在本地可用(file:/// ...)并动态更改Web服务端点来解决这个问题:(可以找到关于为什么需要这样做的好讨论in this forum)
bindingProvider.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, webServiceLocation);
现在WebService被引导并且能够使用命名(别名)客户端证书和相互身份验证通过SSL与服务器对应方进行通信。 ∎
答案 1 :(得分:21)
这就是我基于this post解决它的方法,并进行了一些小的调整。此解决方案不需要创建任何其他类。
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() );
答案 2 :(得分:19)
我尝试了以下操作,但它对我的环境无效:
bindingProvider.getRequestContext().put("com.sun.xml.internal.ws.transport.https.client.SSLSocketFactory", getCustomSocketFactory());
但不同的财产就像一个魅力:
bindingProvider.getRequestContext().put(JAXWSProperties.SSL_SOCKET_FACTORY, getCustomSocketFactory());
其余代码来自第一个回复。
答案 3 :(得分:4)
通过结合Radek和l0co的答案,您可以访问https:
后面的WSDLSSLContext 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
答案 4 :(得分:2)
以上情况很好(正如我在评论中所述),除非你的WSDL也可以通过https://访问。
以下是我的解决方法:
将SSLSocketFactory设置为默认值:
HttpsURLConnection.setDefaultSSLSocketFactory(...);
对于我使用的Apache CXF,您还需要将这些行添加到您的配置中:
<http-conf:conduit name="*.http-conduit">
<http-conf:tlsClientParameters useHttpsURLConnectionDefaultSslSocketFactory="true" />
<http-conf:conduit>
答案 5 :(得分:0)
对于那些尝试但仍未使用它的人来说,这是通过Wildfly 8使用动态Dispatcher
为我做的:
bindingProvider.getRequestContext().put("com.sun.xml.ws.transport.https.client.SSLSocketFactory", yourSslSocketFactory);
请注意,Property键中的internal
部分已移至此处。
答案 6 :(得分:0)
在设置信任管理器时,我在信任自签名证书时遇到了问题。我使用apache httpclient的SSLContexts构建器来创建自定义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
作为参数。
答案 7 :(得分:0)
我尝试了这里的步骤:
http://jyotirbhandari.blogspot.com/2011/09/java-error-invalidalgorithmparameterexc.html
而且,这解决了这个问题。我做了一些小调整 - 我使用System.getProperty设置了两个参数...
答案 8 :(得分:0)
您可以将代理身份验证和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,您可以在处理程序中使用所需的任何库。 玩得开心!
答案 9 :(得分:0)
以下方法对我有用。该答案适用于JAX-WS Axis2实现。我想从maasg的答案中构建。我遇到了与maasg相同的bootsratp问题,并通过使WSDL文件在本地可用来解决了该问题。然后我无法提供SSL上下文,如
bindingProvider.getRequestContext().put("com.sun.xml.internal.ws.transport.https.client.SSLSocketFactory", getCustomSocketFactory());
经过大量的搜索,我从blog中得到了答案。您需要将Apache commons 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);
谢谢。