繁体   English   中英

在 Java 中选择 SSL 客户端证书

[英]Choosing SSL client certificate in Java

我们的系统与多个网络服务提供商进行通信。 它们都是从单个 Java 客户端应用程序调用的。 到目前为止,所有 Web 服务都通过 SSL,但没有一个使用客户端证书。 好吧,一个新的合作伙伴正在改变这一点。

让应用程序使用证书进行调用很容易; 设置javax.net.ssl.keyStorejavax.net.ssl.keyStorePassword就可以了。 然而,现在的问题是如何使它只在调用该特定 Web 服务时使用证书。 我想更一般地说,我们希望能够选择要使用的客户端证书(如果有的话)。

一种快速的解决方案是设置系统属性,调用方法,然后取消设置。 唯一的问题是我们正在处理一个多线程应用程序,所以现在我们需要处理同步或锁或你有什么。

每个服务客户端都应该彼此完全独立,并且它们被单独打包在单独的 JAR 中。 因此,我想到的一个选择(尽管我们还没有正确分析它)是以某种方式隔离每个 JAR,也许将每个 JAR 加载到具有不同参数的不同 VM 下。 这只是一个我不知道如何实现的想法(或者如果它甚至可能,就此而言。)

这篇文章表明可以从密钥库中选择一个单独的证书,但是如何将其附加到请求中似乎完全是一个不同的问题。

我们正在使用 Java 1.5、Axis2 和使用wsimportwsdl2java生成的客户端类。

配置是通过SSLContext完成的,它实际上是SSLSocketFactory (或SSLEngine )的工厂。 默认情况下,这将通过javax.net.ssl.*属性进行配置。 此外,当服务器请求证书时,它会发送一条 TLS/SSL CertificateRequest消息,其中包含它愿意接受的 CA 可分辨名称列表。 虽然这个列表严格来说只是指示性的(即服务器可以接受来自不在列表中的颁发者的证书,或者可以拒绝来自列表中的 CA 的有效证书),但它通常以这种方式工作。

默认情况下,在SSLContext中配置的X509KeyManager中的证书选择器(同样您通常不必担心),将选择列表中的一个已颁发的证书之一(或者可以链接到颁发者那里)。 该列表是X509KeyManager.chooseClientAlias issuersalias是您要选择的证书的别名,如密钥库中所指)。 如果您有多个候选人,您还可以使用socket参数,如果这有助于您做出选择,它将获得对等方的 IP 地址。

如果这有帮助,您可能会发现使用jSSLutils(及其包装器)来配置SSLContext (这些主要是构建SSLContext的辅助类)。 (注意这个例子是选择服务器端的别名,但可以修改, 源代码可用。)

完成此操作后,您应该在 Axis(和 SecureSocketFactory )中查找有关 axis.socketSecureFactory系统属性的文档。 如果您查看 Axis 源代码,构建一个从您选择的 SSLContext初始化的 org.apache.axis.components.net.SunJSSESocketFactory应该不会太难(参见 这个问题)。

刚刚意识到您在谈论 Axis2, SecureSocketFactory似乎已经消失了。 您也许可以使用默认的SSLContext找到解决方法,但这会影响您的整个应用程序(这不是很好)。 如果您使用jSSLutils的 X509KeyManagerWrapper,您可能能够使用默认的X509KeyManager并且仅将某些主机视为例外。 (这不是一个理想的情况,我不确定如何在 Axis 2 中使用自定义SSLContext / SSLSocketFactory 。)

或者,根据 此 Axis 2 文档,看起来 Axis 2 使用 Apache HTTP Client 3.x:

如果你想进行 SSL 客户端认证(2-way SSL),你可以使用 HttpClient 的 Protocol.registerProtocol 特性。 如果您不想与常规 https 混淆,您可以覆盖“https”协议,或者为您的 SSL 客户端身份验证通信使用不同的协议。 http://jakarta.apache.org/commons/httpclient/sslguide.html找到更多信息

在这种情况下, SslContextedSecureProtocolSocketFactory应该帮助您配置SSLContext

Java SSL 客户端仅在服务器请求时才会发送证书。 服务器可以发送关于它将接受哪些证书的可选提示; 如果有多个证书,这将帮助客户选择一个证书。

通常,使用特定客户端证书创建新的SSLContext ,并从从该上下文获取的工厂创建Socket实例。 不幸的是,Axis2 似乎不支持使用SSLContext或自定义SocketFactory 它的客户端证书设置是全局的。

我为不同的端点初始化了 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 中的“https”替换为 https1、https2 或其他内容,具体取决于您为该特定端点初始化的协议):

httpClient.getHostConfiguration().setHost(host, port,Protocol.getProtocol(myProtocolPrefix));
initializeHttpMethod(this.url.toString().replace(HTTPS_PROTOCOL, myProtocolPrefix));

它就像一个魅力!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM