Apache CXF JaxWsProxyFactoryBean 对外部 https 的 SSL 配置调用失败
Apache CXF JaxWsProxyFactoryBean SSL Configuration call to external https fails
我部署了一个 spring 微服务 docker。
我使用 JaxWsProxyFactoryBean 调用外部服务器 (soap/wsdl),使用 http://externalServer:
一切顺利
JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();
jaxWsProxyFactoryBean.setAddress("http://externalServer");
...
在setAddress中调用https时出现的问题
我使用 keytool 在密钥库中注册了被调用服务器的 key/certificate,并保存到 /root/.keystore(标准),从 pfx 导入它。
当我尝试调用 https 时出现此错误:
org.apache.cxf.transport.https.SSLUtils : Default key managers cannot be initialized: Password must not be null
好的,我忘记密码了。但是这个恶意密码放在哪里呢?在 application.yml?在系统 属性 中? [keystore使用的密码是标准的(changeit)]
已编辑
这里是日志的快照:
DEBUG 1 --- [ XNIO-1 task-1] org.apache.cxf.transport.https.SSLUtils : The location of the key store has not been set via a system parameter or through configuration so the default value of /root/.keystore will be used.
DEBUG 1 --- [ XNIO-1 task-1] org.apache.cxf.transport.https.SSLUtils : The key store password has not been set via a system property or through configuration, reading data from the keystore will fail.
DEBUG 1 --- [ XNIO-1 task-1] org.apache.cxf.transport.https.SSLUtils : The key password has not been set via a system property or through configuration, reading data from the keystore will fail.
DEBUG 1 --- [ XNIO-1 task-1] org.apache.cxf.transport.https.SSLUtils : The keystore type has not been set in configuration so the default value of JKS will be used.
WARN 1 --- [ XNIO-1 task-1] org.apache.cxf.transport.https.SSLUtils : Default key managers cannot be initialized: Password must not be null
使用标准密钥库但没有密码。
从您的问题的评论部分查看我们的对话,我可以得出结论,您的 Apache CXF 没有配置 ssl。您需要做的是读取包含受信任证书的密钥库,并使用 TrustManagerFactory 和 TrustManager 将其加载到您的 SSLContext
中。
以下是您可以试用的示例配置:
选项 1 - 普通 Java
import org.apache.cxf.bus.CXFBusFactory;
import org.apache.cxf.configuration.jsse.TLSClientParameters;
import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;
import org.apache.cxf.jaxrs.client.WebClient;
import org.apache.cxf.transport.http.HTTPConduitConfigurer;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import java.nio.file.Paths;
import java.security.KeyStore;
public class App {
public static void main(String[] args) throws Exception {
InputStream identityAsInputStream = Files.newInputStream(Paths.get("/path/to/your/identity.jks"));
KeyStore identity = KeyStore.getInstance(KeyStore.getDefaultType());
identity.load(identityAsInputStream, "keystore-password".toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(identity, "key-password".toCharArray());
KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
InputStream trustStoreInputStream = Files.newInputStream(Paths.get("/path/to/your/truststore.jks"));
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(trustStoreInputStream, "truststore-password".toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null);
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
factory.setAddress("https://some-secure-server.com/");
factory.setBus(new CXFBusFactory().createBus());
factory.getBus().setExtension((name, address, httpConduit) -> {
TLSClientParameters tls = new TLSClientParameters();
tls.setSSLSocketFactory(sslContext.getSocketFactory());
httpConduit.setTlsClientParameters(tls);
}, HTTPConduitConfigurer.class);
WebClient webClient = factory.createWebClient();
}
}
选项 2 - 使用库的简化配置
import nl.altindag.ssl.SSLFactory;
import org.apache.cxf.bus.CXFBusFactory;
import org.apache.cxf.configuration.jsse.TLSClientParameters;
import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;
import org.apache.cxf.jaxrs.client.WebClient;
import org.apache.cxf.transport.http.HTTPConduitConfigurer;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) throws Exception {
SSLFactory sslFactory = SSLFactory.builder()
.withIdentityMaterial(Paths.get("/path/to/your/identity.jks"), "keystore-password".toCharArray())
.withTrustMaterial(Paths.get("/path/to/your/truststore.jks"), "truststore-password".toCharArray())
.build();
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
factory.setAddress("https://some-secure-server.com/");
factory.setBus(new CXFBusFactory().createBus());
factory.getBus().setExtension((name, address, httpConduit) -> {
TLSClientParameters tls = new TLSClientParameters();
tls.setSSLSocketFactory(sslFactory.getSslSocketFactory());
httpConduit.setTlsClientParameters(tls);
}, HTTPConduitConfigurer.class);
WebClient webClient = factory.createWebClient();
}
}
图书馆在这里可用:GitHub - SSLContext Kickstart
我这里还有一个 cxf 客户端的工作示例,用于基于 ssl 的单向和双向身份验证:GitHub - Example Apache CXF client ssl configuration
另一个使用 SSLContextBuilder
来做同样事情的替代方法,它大大简化了信任库的加载:
...
import org.apache.http.ssl.SSLContextBuilder;
public class App {
public static void main(String[] args) throws Exception {
File trustStoreFile = new File("/path/to/keystore.p12");
String trustStorePassword = "truststore-password";
SSLContext sslContext = new SSLContextBuilder()
.loadTrustMaterial(trustStoreFile, trustStorePassword.toCharArray())
.build();
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
factory.setAddress("https://some-secure-server.com/");
factory.setBus(new CXFBusFactory().createBus());
factory.getBus().setExtension((name, address, httpConduit) -> {
TLSClientParameters tls = new TLSClientParameters();
tls.setSSLSocketFactory(sslContext.getSocketFactory());
httpConduit.setTlsClientParameters(tls);
}, HTTPConduitConfigurer.class);
WebClient webClient = factory.createWebClient();
}
}
我部署了一个 spring 微服务 docker。 我使用 JaxWsProxyFactoryBean 调用外部服务器 (soap/wsdl),使用 http://externalServer:
一切顺利JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();
jaxWsProxyFactoryBean.setAddress("http://externalServer");
...
在setAddress中调用https时出现的问题
我使用 keytool 在密钥库中注册了被调用服务器的 key/certificate,并保存到 /root/.keystore(标准),从 pfx 导入它。 当我尝试调用 https 时出现此错误:
org.apache.cxf.transport.https.SSLUtils : Default key managers cannot be initialized: Password must not be null
好的,我忘记密码了。但是这个恶意密码放在哪里呢?在 application.yml?在系统 属性 中? [keystore使用的密码是标准的(changeit)]
已编辑
这里是日志的快照:
DEBUG 1 --- [ XNIO-1 task-1] org.apache.cxf.transport.https.SSLUtils : The location of the key store has not been set via a system parameter or through configuration so the default value of /root/.keystore will be used.
DEBUG 1 --- [ XNIO-1 task-1] org.apache.cxf.transport.https.SSLUtils : The key store password has not been set via a system property or through configuration, reading data from the keystore will fail.
DEBUG 1 --- [ XNIO-1 task-1] org.apache.cxf.transport.https.SSLUtils : The key password has not been set via a system property or through configuration, reading data from the keystore will fail.
DEBUG 1 --- [ XNIO-1 task-1] org.apache.cxf.transport.https.SSLUtils : The keystore type has not been set in configuration so the default value of JKS will be used.
WARN 1 --- [ XNIO-1 task-1] org.apache.cxf.transport.https.SSLUtils : Default key managers cannot be initialized: Password must not be null
使用标准密钥库但没有密码。
从您的问题的评论部分查看我们的对话,我可以得出结论,您的 Apache CXF 没有配置 ssl。您需要做的是读取包含受信任证书的密钥库,并使用 TrustManagerFactory 和 TrustManager 将其加载到您的 SSLContext
中。
以下是您可以试用的示例配置:
选项 1 - 普通 Java
import org.apache.cxf.bus.CXFBusFactory;
import org.apache.cxf.configuration.jsse.TLSClientParameters;
import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;
import org.apache.cxf.jaxrs.client.WebClient;
import org.apache.cxf.transport.http.HTTPConduitConfigurer;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import java.nio.file.Paths;
import java.security.KeyStore;
public class App {
public static void main(String[] args) throws Exception {
InputStream identityAsInputStream = Files.newInputStream(Paths.get("/path/to/your/identity.jks"));
KeyStore identity = KeyStore.getInstance(KeyStore.getDefaultType());
identity.load(identityAsInputStream, "keystore-password".toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(identity, "key-password".toCharArray());
KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
InputStream trustStoreInputStream = Files.newInputStream(Paths.get("/path/to/your/truststore.jks"));
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(trustStoreInputStream, "truststore-password".toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null);
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
factory.setAddress("https://some-secure-server.com/");
factory.setBus(new CXFBusFactory().createBus());
factory.getBus().setExtension((name, address, httpConduit) -> {
TLSClientParameters tls = new TLSClientParameters();
tls.setSSLSocketFactory(sslContext.getSocketFactory());
httpConduit.setTlsClientParameters(tls);
}, HTTPConduitConfigurer.class);
WebClient webClient = factory.createWebClient();
}
}
选项 2 - 使用库的简化配置
import nl.altindag.ssl.SSLFactory;
import org.apache.cxf.bus.CXFBusFactory;
import org.apache.cxf.configuration.jsse.TLSClientParameters;
import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;
import org.apache.cxf.jaxrs.client.WebClient;
import org.apache.cxf.transport.http.HTTPConduitConfigurer;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) throws Exception {
SSLFactory sslFactory = SSLFactory.builder()
.withIdentityMaterial(Paths.get("/path/to/your/identity.jks"), "keystore-password".toCharArray())
.withTrustMaterial(Paths.get("/path/to/your/truststore.jks"), "truststore-password".toCharArray())
.build();
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
factory.setAddress("https://some-secure-server.com/");
factory.setBus(new CXFBusFactory().createBus());
factory.getBus().setExtension((name, address, httpConduit) -> {
TLSClientParameters tls = new TLSClientParameters();
tls.setSSLSocketFactory(sslFactory.getSslSocketFactory());
httpConduit.setTlsClientParameters(tls);
}, HTTPConduitConfigurer.class);
WebClient webClient = factory.createWebClient();
}
}
图书馆在这里可用:GitHub - SSLContext Kickstart
我这里还有一个 cxf 客户端的工作示例,用于基于 ssl 的单向和双向身份验证:GitHub - Example Apache CXF client ssl configuration
另一个使用 SSLContextBuilder
来做同样事情的替代方法,它大大简化了信任库的加载:
...
import org.apache.http.ssl.SSLContextBuilder;
public class App {
public static void main(String[] args) throws Exception {
File trustStoreFile = new File("/path/to/keystore.p12");
String trustStorePassword = "truststore-password";
SSLContext sslContext = new SSLContextBuilder()
.loadTrustMaterial(trustStoreFile, trustStorePassword.toCharArray())
.build();
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
factory.setAddress("https://some-secure-server.com/");
factory.setBus(new CXFBusFactory().createBus());
factory.getBus().setExtension((name, address, httpConduit) -> {
TLSClientParameters tls = new TLSClientParameters();
tls.setSSLSocketFactory(sslContext.getSocketFactory());
httpConduit.setTlsClientParameters(tls);
}, HTTPConduitConfigurer.class);
WebClient webClient = factory.createWebClient();
}
}