我们的系统与多个Web服务提供商进行通信。它们都是从单个Java客户端应用程序调用的。到目前为止,所有Web服务都已通过SSL,但没有一个使用客户端证书。好吧,一个新的合作伙伴正在改变它。
使应用程序使用证书进行调用很容易;设置javax.net.ssl.keyStore
和javax.net.ssl.keyStorePassword
即可。但是,现在的问题是如何使它在调用特定Web服务时仅使用证书。我想更一般地说,我们希望能够选择要使用的客户端证书(如果有的话)。
一个快速的解决方案是设置系统属性,调用方法,然后取消设置。唯一的问题是我们正在处理多线程应用程序,所以现在我们需要处理同步或锁定或者你有什么。
每个服务客户端应该完全相互独立,并且它们分别打包在单独的JAR中。因此,我发现的一个选项(虽然我们没有对其进行适当的分析)是以某种方式隔离每个JAR,可能在具有不同参数的不同VM下加载每个JAR。这仅仅是一个我不知道如何实施的想法(或者如果可能的话,就此而言。)
This post表示可以从密钥库中选择单个证书,但如何将其附加到请求似乎完全是另一个问题。
我们使用的是使用wsimport
或wsdl2java
生成的Java 1.5,Axis2和客户端类。
答案 0 :(得分:14)
配置是通过SSLContext
完成的,SSLSocketFactory
实际上是SSLEngine
(或javax.net.ssl.*
)的工厂。默认情况下,这将从CertificateRequest
属性配置。此外,当服务器请求证书时,它会发送一条TLS / SSL X509KeyManager
消息,其中包含它愿意接受的CA的可分辨名称列表。虽然这个列表严格来说只是指示性的(即服务器可以接受不在列表中的发行者的证书,或者可以拒绝列表中CA的有效证书),但它通常以这种方式工作。
默认情况下,SSLContext
中配置的issuers
中的证书选择器(通常您不必担心),将选择一个已颁发的证书之一在列表中(或可以链接到那里的发行人)。
该列表是X509KeyManager.chooseClientAlias
中的alias
参数(socket
是您要选择的证书的别名,如密钥库中所引用的)。如果您有多个候选人,您还可以使用SSLContext
参数,如果这有助于您做出选择,它将为您提供对等方的IP地址。
如果这有帮助,您可能会发现使用jSSLutils (and its wrapper)来配置SSLContext
(这些主要是帮助类来构建axis.socketSecureFactory
)。 (请注意,此示例用于选择服务器端别名,但可以对其进行调整,source code is available。)
完成此操作后,您应该在Axis(和 < / p>
SecureSocketFactory
)中查找有关org.apache.axis.components.net.SunJSSESocketFactory
系统属性的文档。如果您查看Axis源代码,那么构建从您选择的SSLContext
初始化的SecureSocketFactory
应该不会太困难(请参阅this question)。
刚刚意识到你在谈论Axis2,SSLContext
似乎已经消失了。您可以使用默认的X509KeyManager
找到解决方法,但这会影响整个应用程序(这不是很好)。如果使用X509KeyManagerWrapper jSSLutils,则可以使用默认的SSLContext
并仅将某些主机视为例外。 (这不是一个理想的情况,我不确定如何在Axis 2中使用自定义SSLSocketFactory
/ SSLContext
。)
或者,根据this Axis 2 document,看起来Axis 2使用Apache HTTP Client 3.x:
如果要执行SSL客户端 验证(双向SSL),你可以 使用Protocol.registerProtocol HttpClient的功能。您可以 覆盖“https”协议,或使用 SSL的不同协议 客户端认证通信 如果你不想乱用常规 HTTPS。有关详细信息,请访问 http://jakarta.apache.org/commons/httpclient/sslguide.html
在这种情况下,SslContextedSecureProtocolSocketFactory
应该可以帮助您配置{{1}}。
答案 1 :(得分:8)
Java SSL客户端仅在服务器请求时才发送证书。服务器可以发送关于它将接受哪些证书的可选提示;这将有助于客户选择单个证书(如果有多个证书)。
通常,使用特定客户端证书创建新的SSLContext
,并从从该上下文获取的工厂创建Socket
个实例。很遗憾,Axis2似乎不支持使用SSLContext
或自定义SocketFactory
。其客户端证书设置是全局的。
答案 2 :(得分:0)
我为不同的端点初始化EasySSLProtocolSocketFactory和Protocol实例,并使用如下唯一键注册协议:
/**
* This method does the following:
* 1. Creates a new and unique protocol for each SSL URL that is secured by client certificate
* 2. Bind keyStore related information to this protocol
* 3. Registers it with HTTP Protocol object
* 4. Stores the local reference for this custom protocol for use during furture collect calls
*
* @throws Exception
*/
public void registerProtocolCertificate() throws Exception {
EasySSLProtocolSocketFactory easySSLPSFactory = new EasySSLProtocolSocketFactory();
easySSLPSFactory.setKeyMaterial(createKeyMaterial());
myProtocolPrefix = (HTTPS_PROTOCOL + uniqueCounter.incrementAndGet());
Protocol httpsProtocol = new Protocol(myProtocolPrefix,(ProtocolSocketFactory) easySSLPSFactory, port);
Protocol.registerProtocol(myProtocolPrefix, httpsProtocol);
log.trace("Protocol [ "+myProtocolPrefix+" ] registered for the first time");
}
/**
* Load keystore for CLIENT-CERT protected endpoints
*/
private KeyMaterial createKeyMaterial() throws GeneralSecurityException, Exception {
KeyMaterial km = null;
char[] password = keyStorePassphrase.toCharArray();
File f = new File(keyStoreLocation);
if (f.exists()) {
try {
km = new KeyMaterial(keyStoreLocation, password);
log.trace("Keystore location is: " + keyStoreLocation + "");
} catch (GeneralSecurityException gse) {
if (logErrors){
log.error("Exception occured while loading keystore from the following location: "+keyStoreLocation, gse);
throw gse;
}
}
} else {
log.error("Unable to load Keystore from the following location: " + keyStoreLocation );
throw new CollectorInitException("Unable to load Keystore from the following location: " + keyStoreLocation);
}
return km;
}
当我必须调用Web服务时,我这样做(基本上用URL替换https1中的“https”,或https2或其他东西,具体取决于您为该特定端点初始化的协议):
httpClient.getHostConfiguration().setHost(host, port,Protocol.getProtocol(myProtocolPrefix));
initializeHttpMethod(this.url.toString().replace(HTTPS_PROTOCOL, myProtocolPrefix));
它就像一个魅力!