如何以编程方式设置 JAX-WS 客户端的 SSLContext?
Posted
技术标签:
【中文标题】如何以编程方式设置 JAX-WS 客户端的 SSLContext?【英文标题】:How to programmatically set the SSLContext of a JAX-WS client? 【发布时间】:2012-06-15 14:20:28 【问题描述】:我正在一个分布式应用程序中的服务器上工作,该应用程序具有浏览器客户端,并且还参与与第 3 方的服务器到服务器通信。 我的服务器有一个 CA 签名证书,让我的客户端使用 HTTP/S 和 XMPP(安全)使用 TLS (SSL) 通信进行连接。一切正常。
现在我需要通过 HTTPS/SSL 使用 JAX-WS 安全地连接到第 3 方服务器。在此通信中,我的服务器在 JAX-WS 交互中充当客户端,并且我有一个由第 3 方签署的客户端证书。
我尝试通过标准系统配置 (-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
有什么需要吗?
【问题讨论】:
错误报告称 2.1.1 将提供修复。 @EJP 该错误报告是一个功能请求,并没有提及应该/将如何完成。 【参考方案1】:这是一个难以破解的难题,因此记录在案:
为了解决这个问题,它需要一个自定义KeyManager
和一个使用此自定义KeyManager
的SSLSocketFactory
来访问分隔的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 与服务器对应方通信。 ∎
【讨论】:
这看起来只有在你的 WSDL 可以在 http:// 下访问时才有用,但是如果它也在 https:// 下呢?客户端在第一步失败(获取 WSDL)。 我们遇到了同样的情况,最终在本地获得了 wsdl 的副本。否则你就会遇到这个引导问题。 仅供参考: 如果您的应用程序使用 JAXWS-RI,那么您应该使用稍微不同的属性:com.sun.xml.ws.transport.https.client.SSLSocketFactory
。我指定两者以满足单元测试和实际应用环境中的不同堆栈(不要问)。
@mvreijn,你救了我的命……我知道有代表这些文字字符串的常量……你知道它们是什么吗?
@RafaelLima 我检查了我的源代码文档(它存在!)并且我记录了指向jax-ws.java.net/nonav/2.2.8/javadocs/rt/constant-values.html 的链接。不幸的是,该项目已移动,因此常量列表似乎位于不同的位置。【参考方案2】:
这就是我基于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() );
【讨论】:
上面代码中定义的webservicePort在哪里(在(BindingProvider)转换后的最后一行代码中使用? @Radek,介意说明 webservicePort 的类型是什么?看起来是 javax.xml.ws.Service 但不确定。 伙计们,我在 2012 年发布了这个。老实说,我不记得我在哪里定义了那个端口。抱歉,没有解释,但我无法再访问该代码来实际检查并让您知道。我可能已经从注入的参数中获取了它,而不必自己定义它,不确定。我想如果您对 BindingProvider 以及实例化它的内容进行一些研究,应该不难弄清楚。如果我当时让它工作,你也可以。祝你好运! @John webservicePort 在 wsimport 生成的类中定义。 非常感谢 :) 这让我很开心!【参考方案3】:我尝试了以下方法,但在我的环境中不起作用:
bindingProvider.getRequestContext().put("com.sun.xml.internal.ws.transport.https.client.SSLSocketFactory", getCustomSocketFactory());
但是不同的属性就像一个魅力:
bindingProvider.getRequestContext().put(JAXWSProperties.SSL_SOCKET_FACTORY, getCustomSocketFactory());
其余代码取自第一个回复。
【讨论】:
什么版本的一切?看起来不同版本的wsimport
会生成不同类型的代码......它们也可能需要不同的属性名称。
当您将 jaxws-rt JAR 显式添加到应用程序时,您需要使用不包含 .internal.
的属性名称。如果您使用 JDK 中包含的 JAXWS-RT,您需要使用包含“.internal.”的那些。 JAXWSProperties.SSL_SOCKET_FACTORY
解析为 "com.sun.xml.ws.transport.https.client.SSLSocketFactory"
【参考方案4】:
通过结合 Radek 和 l0co 的答案,您可以访问 https 后面的 WSDL:
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
【讨论】:
getResourceAsStream(keystore)
中的keystore
是指证书文件的路径吗?
在这种情况下,它是类路径中的路径。如果需要绝对文件路径,可以使用 File 代替 getClass().getResourceAsStream()。
对我来说不太好,因为我进行了其他调用,我想使用默认的 ssl 套接字工厂,这会将它们塞满
为我工作。谢谢你的好回答。【参考方案5】:
您可以将您的代理身份验证和 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,您可以在处理程序中使用您想要的任何库。 玩得开心!
【讨论】:
【参考方案6】:除非您的 WSDL 也可以通过 https:// 访问,否则上述内容很好(正如我在评论中所说)。
这是我的解决方法:
将 SSLSocketFactory 设置为默认值:
HttpsURLConnection.setDefaultSSLSocketFactory(...);
对于我使用的 Apache CXF,您还需要将这些行添加到您的配置中:
<http-conf:conduit name="*.http-conduit">
<http-conf:tlsClientParameters useHttpsURLConnectionDefaultSslSocketFactory="true" />
<http-conf:conduit>
【讨论】:
【参考方案7】:对于那些尝试但仍然无法正常工作的人,我使用 Wildfly 8 完成了这项工作,使用动态 Dispatcher
:
bindingProvider.getRequestContext().put("com.sun.xml.ws.transport.https.client.SSLSocketFactory", yourSslSocketFactory);
请注意,Property 键中的 internal
部分已在此处消失。
【讨论】:
我使用的是 Wildfly 8,但它对我也不起作用。 (dynamic Dispatcher 是什么意思? - 请查看我的帖子:***.com/questions/37158821/… 和 ***.com/questions/37158821/…【参考方案8】:在设置信任管理器时,我在信任自签名证书时遇到了问题。我使用了 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
方法中的参数传递。
【讨论】:
【参考方案9】:我尝试了这里的步骤:
http://jyotirbhandari.blogspot.com/2011/09/java-error-invalidalgorithmparameterexc.html
而且,这解决了问题。我做了一些小调整 - 我使用 System.getProperty 设置了两个参数...
【讨论】:
请引用链接中最相关的部分,以防目标站点无法访问或永久离线。 这个答案将为整个应用程序设置信任库。相关部分:-Djavax.net.ssl.trustStore=$JAVA_HOME/jre/lib/security/cacerts -Djavax.net.ssl.trustStorePassword=password
【参考方案10】:
由于系统集成之间的密钥库冲突,我们遇到了这个问题,因此我们使用了以下代码。
private PerSecurityWS prepareConnectionPort()
final String HOST_BUNDLE_SYMBOLIC_NAME = "wpp.ibm.dailyexchangerates";
final String PATH_TO_SLL = "ssl/<your p.12 certificate>";
final File ksFile = getFile(HOST_BUNDLE_SYMBOLIC_NAME, PATH_TO_SLL);
final String serverURI = "you url";
final KeyStore keyStore = KeyStore.getInstance("pkcs12");
keyStore.load(new FileInputStream(ksFile.getAbsolutePath()), keyStorePassword.toCharArray());
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, keyStorePassword.toCharArray());
final HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier()
@Override
public boolean verify(final String hostname, final SSLSession session)
return false;
;
final SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(kmf.getKeyManagers(), null, null);
final SSLSocketFactory sslSocketFactory = ctx.getSocketFactory();
final PerSecurityWS port = new PerSecurityWS_Service().getPerSecurityWSPort();
final BindingProvider bindingProvider = (BindingProvider) port;
bindingProvider.getRequestContext().put("com.sun.xml.internal.ws.transport.https.client.SSLSocketFactory",sslSocketFactory);
bindingProvider.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, serverURI);
bindingProvider.getRequestContext().put("com.sun.xml.internal.ws.transport.https.client.hostname.verifier",DO_NOT_VERIFY);
return port;
【讨论】:
以上是关于如何以编程方式设置 JAX-WS 客户端的 SSLContext?的主要内容,如果未能解决你的问题,请参考以下文章