在Java中selectSSL客户端证书
我们的系统与多个Web服务提供商进行通信 它们都是从一个Java客户端应用程序调用的。 到目前为止,所有的Web服务都已经通过SSL,但是没有一个使用客户端证书。 那么,一个新的伙伴正在改变这一点。
使应用程序使用证书进行调用很容易; 设置javax.net.ssl.keyStore
和javax.net.ssl.keyStorePassword
将做到这一点。 但是,现在的问题是如何使它只在调用特定的Web服务时使用证书。 我想更一般地说,我们希望能够select使用的客户端证书,如果有的话。
一个快速的解决scheme可能是设置系统属性,调用方法,然后取消它们。 唯一的问题是我们正在处理一个multithreading的应用程序,所以现在我们需要处理同步或locking或你有什么。
每个服务客户端应该是完全独立的,而且它们被单独打包在单独的JAR中。 因此,我所遇到的一个select(尽pipe我们没有正确地分析它)是以某种方式隔离每个JAR,或者将每个JAR加载到具有不同参数的不同VM上。 这仅仅是一个我不知道如何实现的想法(或者如果甚至可能的话)。
这篇文章build议可以从密钥库中select一个单独的证书,但是如何将它附加到请求中似乎完全是一个不同的问题。
我们使用由wsimport
或wsdl2java
生成的Java 1.5,Axis2和客户端类。
Java SSL客户端只会在服务器请求时发送证书。 服务器可以发送关于它将接受哪些证书的可选提示; 这将有助于客户select一个单一的证书,如果它有多个。
通常,使用特定的客户端证书创build新的SSLContext
,并通过从该上下文获得的工厂创buildSocket
实例。 不幸的是,Axis2似乎不支持使用SSLContext
或自定义的SocketFactory
。 它的客户端证书设置是全局的。
configuration是通过一个SSLContext
完成的, SSLContext
实际上是SSLSocketFactory
(或SSLEngine
)的一个工厂。 默认情况下,这将通过javax.net.ssl.*
属性进行configuration。 另外,当一个服务器请求一个证书时,它发送一个TLS / SSL CertificateRequest
请求消息,该消息包含它愿意接受的CA的可分辨名称列表。 虽然这个清单严格地说只是指示性的(即服务器可以接受来自不在列表中的发行者的证书,或者可以拒绝来自列表中的CA的有效证书),但它通常以这种方式工作。
默认情况下,在SSLContext
configuration的X509KeyManager
的证书select器(您通常不必担心),将select由列表中的一个发行的证书(或者可以链接到发行者那里)。 该列表是X509KeyManager.chooseClientAlias
中的X509KeyManager.chooseClientAlias
参数( alias
是您要在密钥库中引用的证书的别名)。 如果您有多个候选人,您也可以使用socket
参数,这将使您获得对等的IP地址,如果这有助于做出select。
如果这有帮助,你可能会发现使用jSSLutils(及其包装)来configuration你的SSLContext
(这些主要是用来构buildSSLContext
的帮助类)。 (请注意,这个例子是用来select服务器端的别名,但是可以调整, 源代码可用 。)
完成此操作后,应查找有关Axis(和 SecureSocketFactory
)中的axis.socketSecureFactory
系统属性的文档。如果你看看Axis的源代码,build立一个 org.apache.axis.components.net.SunJSSESocketFactory
并不难,因为它是从你select的SSLContext
初始化的(参见这个问题 )。
刚刚意识到你正在谈论Axis2,那里的SecureSocketFactory
似乎已经消失。 您可能能够使用默认的SSLContext
find解决方法,但这会影响您的整个应用程序(这不是很好)。 如果您使用jSSLutils的X509KeyManagerWrapper,则可以使用默认的X509KeyManager
并仅将某些主机视为例外。 (这不是一个理想的情况,我不知道如何在Axis 2中使用自定义的SSLContext
/ SSLSocketFactory
。)
另外,根据这个Axis 2文档 ,它看起来像Axis 2使用Apache HTTP客户端3.x:
如果您想执行SSL客户端身份validation(双向SSL),则可以使用HttpClient的Protocol.registerProtocolfunction。 您可以覆盖“https”协议,或者如果您不想使用普通的https,则可以使用不同的协议来进行SSL客户端身份validation通信。 有关详细信息,请访问http://jakarta.apache.org/commons/httpclient/sslguide.html
在这种情况下, SslContextedSecureProtocolSocketFactory
应该可以帮助您configuration一个SSLContext
。
我为不同的端点初始化了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服务时,我这样做(基本上用https1或https2replaceURL中的“https”,具体取决于您为特定端点初始化的协议):
httpClient.getHostConfiguration().setHost(host, port,Protocol.getProtocol(myProtocolPrefix)); initializeHttpMethod(this.url.toString().replace(HTTPS_PROTOCOL, myProtocolPrefix));
它就像一个魅力!