Java多HTTPS SOAP WebService客户端SSL配置冲突问题问询
我有一个Java Web应用,包含两个HTTPS SOAP WebService客户端:
https://product.com?wsdl https://sales.com?wsdl
每个WebService对应独立的Java类(如Products.java和Sales.java),各自实现WebService调用逻辑,配置了专属的trustStore、trustStorePassword、keyStore、keyStorePassword、keyStoreType等SSL信息。
我清楚这些SSL配置在Java中是全局生效的,且两个类都采用单例模式实例化。实际调用时,首次调用https://product.com?wsdl正常,接着调用https://sales.com?wsdl也正常,但再次调用https://product.com?wsdl时失败。原因是单例实例化后,SSL信任库和密钥库信息没有根据目标服务重新配置,全局SSL设置被后续服务覆盖了。
目前可行的方案是调用前刷新SSL信息,或者不使用单例,但后者可能增加内存占用,不是理想选择。现在提出两个问题:
- 调用特定WebService前是否必须刷新信任库和密钥库信息?
- 不使用单例模式实例化客户端会有哪些弊端?
相关代码
Products.java
public class Products { private static Products instance = null; public ProductsWS webServiceProduct = null; ... ... protected Products() { try{ KeyManager[] keyManager = this.getKeyManagers(productKeyStore, productKeyStorePassword); TrustManager[] trustManager = this.getTrustManagers(productTrustStore, productTrustStorePassword); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(keyManager, trustManager, null); HostnameVerifier hostnameVerifier = new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) { return true; } }; HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier); HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory()); QName qname = new QName("http://product.net/", "ProductsWSService"); ProductsWSService productsWSService = new ProductsWSService(new URL(productUrl),qname); webServiceProduct = productsWSService.getProductsWSPort(); System.out.println("Connection succeded"); }catch(Exception ex){ System.out.println("Error"); } } public static Products getInstance(){ if(instance == null){ instance = new Products(); } return instance; } public String getProductName(int number) { if (number==1) return "Radio"; else return "Television"; } }
Sales.java
public class Sales { private static Sales instance = null; public SalesWS webServiceSale = null; ... ... protected Sales() { try{ KeyManager[] keyManager = this.getKeyManagers(saleKeyStore, saleKeyStorePassword); TrustManager[] trustManager = this.getTrustManagers(saleTrustStore, saleTrustStorePassword); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(keyManager, trustManager, null); HostnameVerifier hostnameVerifier = new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) { return true; } }; HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier); HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory()); QName qname = new QName("http://sale.net/", "SalesWSService"); SalesWSService salesWSService = new SalesWSService(new URL(productUrl),qname); webServiceProduct = salesWSService.getSalesWSPort(); System.out.println("Connection succeded"); }catch(Exception ex){ System.out.println("Error"); } } public static Sales getInstance(){ if(instance == null){ instance = new Sales(); } return instance; } public String getSaleDate(int number) { if (number==1) return "2023-01-01"; else return "2023-02-01"; } }
Client.java
public class Client { protected Client() { } public static void main(String args[]){ Products clientProduct = Products.getInstance(); String name = clientProduct.getProductName(1); // It works Sales saleProduct = Sales.getInstance(); String date = saleProduct.getSaleDate(1); // It works name = clientProduct.getProductName(1); // It fails because the objects were instanced in singleton. //Besides, the trustore and keystore information were not refreshed according to specific webservice } }
问题解答
1. 调用特定WebService前是否必须刷新信任库和密钥库信息?
不是必须,但如果继续使用修改全局SSL配置的方式,就需要在每次切换服务调用前刷新。因为当前实现是通过HttpsURLConnection.setDefaultSSLSocketFactory和setDefaultHostnameVerifier修改全局默认配置,所有后续HTTPS请求都会共享这个配置,导致切换服务时出现冲突。
更优的方案是避免修改全局SSL配置,为每个WebService客户端绑定专属的SSL上下文:
- 生成各自的
SSLContext后,不要设置为全局默认,而是通过JAX-WS的BindingProvider为特定端口设置专属的SSL套接字工厂; - 或者直接为单个
URLConnection调用setSSLSocketFactory方法,指定专属SSL上下文。
这样每个客户端的SSL配置相互独立,无需每次调用前刷新,也不会互相覆盖。
2. 不使用单例模式实例化客户端会有哪些弊端?
- 内存占用上升:每次调用都创建新的客户端实例,包括SSL上下文、WebService端口等对象,频繁调用会增加内存开销;
- 初始化耗时增加:每次实例化都需要加载密钥库、信任库,初始化SSL上下文,创建WebService端口,这些操作耗时较高,会降低调用性能;
- 资源重复消耗:密钥库、信任库的加载和解析属于重复操作,会浪费CPU和IO资源;
- 连接复用率低:如果WebService客户端内部维护连接池,非单例会导致每个实例拥有独立的连接池,无法有效复用连接,进一步降低性能。
不过这些弊端可以通过对象池模式缓解:维护一个客户端实例池,需要时从池中获取,用完放回,既避免单例的全局SSL冲突,又能复用实例减少资源消耗。
内容的提问来源于stack exchange,提问作者user3637971

