将 Java 客户端 (JMS) 连接到 IBM MQ 时出现问题
Problem Connecting a Java Client (JMS) to a IBM MQ
我正在尝试使用 IBM MQ(版本 8.0.0.8)和 SSL 使用 Java 客户端,基本上构建于:
- Oracle JKD 8 和 IBM JRE 7(出于测试目的,我为它们各自准备了一个客户端)
- com.ibm.mq.allclient-9.1.0.0.jar
- javax.jms-api-2.0.1.jarspring-jms-4.3.7.RELEASE.jar
- spring-jms-4.3.7.RELEASE.jar
MQ 是 Request/Reply 类型。
我有正确的证书并设置了所有 MQ 属性,但由于某种原因连接“断开”并且我在客户端没有收到任何错误并且我的请求从未得到任何响应并保持 运行“永远”,从未得到任何回应。我唯一的线索是 MQ 日志中的一条错误消息:
Process(31600.16) User(QMQM) Jobname(JOB_NAME)
Host(HOST_NAME)
VRMF(8.0.0.8) QMgr(MANAGER_NAME)
AMQ9638: SSL communications error for channel 'CHANNEL_NAME'. EXPLANATION:
Cause . . . . . : An unexpected SSL communications error occurred for a channel, as reported in the preceding messages. The
channel is 'CHANNEL_NAME';
奇怪的是发生了 SSL 握手,我的证书被 MQ 接受了,但由于某种原因在此之后发生了一些事情。我正在尝试同时使用 Oracle JRE 8 和 IBM JRE 7。可能是 MQ 方面的东西 (IBM MQ v8.0.0.8) 或我这边缺少的某些配置。
我已经安装了 JCE Unlimited Policies,所以问题不是 CipherSpec X CipherSuite。
我正在使用 -Djavax.net.debug=all 并且我可以看到我的证书被正确使用并且我看不到任何问题...
我在 MQ 团队的联系人告诉我,出于某种原因,我的应用程序正在吊销证书(与 CLR 相关),但我不知道为什么会发生这种情况。
我的Java代码:
public Message callMQ() {
Message message = null;
try {
MQConnectionFactory factory = mqQueueConnectionFactory();
JMSContext context = factory.createContext();
Destination requestQueue = context.createQueue("queue:///REQUEST_QUEUE");
Destination replyQueue = context.createQueue("queue:///REPLY_QUEUE");
JmsTemplate jmsTemplate = new JmsTemplate(factory);
FIXMLRootInbound inbound = new FIXMLRootInbound();
String xml = XmlUtil.xmlObjectToString(inbound);
message = jmsTemplate.sendAndReceive(requestQueue,
session -> {
Message req = session.createTextMessage(xml);
req.setJMSCorrelationID(UUID.randomUUID().toString());
req.setJMSDestination(requestQueue);
req.setJMSReplyTo(replyQueue);
return req;
});
} catch (Throwable e) {
e.printStackTrace();
}
return message;
}
private MQConnectionFactory mqQueueConnectionFactory() throws NoSuchAlgorithmException, KeyStoreException,
IOException, CertificateException, UnrecoverableKeyException, KeyManagementException, JmsException {
SSLSocketFactory sslSocketFactory = sslContext().getSocketFactory();
MQEnvironment.sslSocketFactory = sslSocketFactory;
MQEnvironment.sslCipherSuite = "TLS_RSA_WITH_AES_256_CBC_SHA";
MQEnvironment.sslFipsRequired = false;
MQConnectionFactory mqQueueConnectionFactory = new MQQueueConnectionFactory();
mqQueueConnectionFactory.setHostName(host);
try {
mqQueueConnectionFactory.setTransportType(JMSC.MQJMS_TP_CLIENT_MQ_TCPIP);
mqQueueConnectionFactory.setIntProperty(WMQConstants.WMQ_CONNECTION_MODE,
WMQConstants.WMQ_CM_CLIENT);
mqQueueConnectionFactory.setQueueManager(queueManager);
mqQueueConnectionFactory.setSSLCipherSuite("TLS_RSA_WITH_AES_256_CBC_SHA");
mqQueueConnectionFactory.setCCSID(285);
mqQueueConnectionFactory.setChannel(channel);
mqQueueConnectionFactory.setPort(port);
mqQueueConnectionFactory.setSSLSocketFactory(sslSocketFactory);
mqQueueConnectionFactory.setSSLFipsRequired(false);
} catch (Exception e) {
log.error("Error creating MQQueueConnectionFactory.", e);
}
return mqQueueConnectionFactory;
}
private SSLContext sslContext() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
try (InputStream cert = new FileInputStream("C:\myplace\Dev\Certificates\MY_KEYSTORE.jks")) {
final KeyStore caCertsKeyStore = KeyStore.getInstance("JKS");
caCertsKeyStore.load(cert, "changeit".toCharArray());
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX");
PKIXRevocationChecker rc = (PKIXRevocationChecker)cpb.getRevocationChecker();
rc.setOptions(EnumSet.of(
PKIXRevocationChecker.Option.PREFER_CRLS,
PKIXRevocationChecker.Option.ONLY_END_ENTITY,
PKIXRevocationChecker.Option.SOFT_FAIL,
PKIXRevocationChecker.Option.NO_FALLBACK));
PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(caCertsKeyStore, new X509CertSelector());
pkixParams.addCertPathChecker(rc);
kmf.init(caCertsKeyStore, "changeit".toCharArray());
tmf.init( new CertPathTrustManagerParameters(pkixParams) );
final SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
return sslContext;
} catch (Exception e) {
throw new RuntimeException("Exception creating SSLContext", e);
}
}
由于您使用的是 9.1.0.0 com.ibm.mq.allclient.jar
,因此您不需要所有与密钥库相关的代码,例如:
SSLSocketFactory sslSocketFactory = sslContext().getSocketFactory();
//Note that MQEnvironment is used with IBM MQ Classes for Java not IBM MQ Classes for JMS
MQEnvironment.sslSocketFactory = sslSocketFactory;
MQEnvironment.sslCipherSuite = "TLS_RSA_WITH_AES_256_CBC_SHA";
MQEnvironment.sslFipsRequired = false;
mqQueueConnectionFactory.setSSLSocketFactory(sslSocketFactory);
private SSLContext sslContext() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
try (InputStream cert = new FileInputStream("C:\myplace\Dev\Certificates\MY_KEYSTORE.jks")) {
final KeyStore caCertsKeyStore = KeyStore.getInstance("JKS");
caCertsKeyStore.load(cert, "changeit".toCharArray());
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX");
PKIXRevocationChecker rc = (PKIXRevocationChecker)cpb.getRevocationChecker();
rc.setOptions(EnumSet.of(
PKIXRevocationChecker.Option.PREFER_CRLS,
PKIXRevocationChecker.Option.ONLY_END_ENTITY,
PKIXRevocationChecker.Option.SOFT_FAIL,
PKIXRevocationChecker.Option.NO_FALLBACK));
PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(caCertsKeyStore, new X509CertSelector());
pkixParams.addCertPathChecker(rc);
kmf.init(caCertsKeyStore, "changeit".toCharArray());
tmf.init( new CertPathTrustManagerParameters(pkixParams) );
final SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
return sslContext;
} catch (Exception e) {
throw new RuntimeException("Exception creating SSLContext", e);
}
}
您可以将其替换为设置以下两个系统属性,这将适用于 Oracle 和 IBM Java:
System.setProperty("javax.net.ssl.keyStore", "C:\myplace\Dev\Certificates\MY_KEYSTORE.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "changeit");
以上设置一直适用于 IBM Java,但对于 Oracle Java 这不适用于旧版本的 MQ。在以下 IBM MQ 版本中针对 Oracle java 进行了修复(Base 9.0 和 9.1 具有相同的修复):
Version Maintenance Level
v7.1 7.1.0.8
v7.5 7.5.0.7
v8.0 8.0.0.5
IBM Java 和 Oracle Java 具有不同的 CipherSuite 名称,这些名称记录在 IBM MQ v9.1 知识中心页面“TLS CipherSpecs and CipherSuites in IBM MQ classes for JMS.
您在 posted 代码中指定了 TLS_RSA_WITH_AES_256_CBC_SHA
,这将是 MQ 队列管理器 SVRCONN
通道上的 SSLCIPH
值,并将映射到以下 CipherSuites:
- IBM Java:
SSL_RSA_WITH_AES_256_CBC_SHA
- 甲骨文 Java:
TLS_RSA_WITH_AES_256_CBC_SHA
与上述相关,如果您使用的是Oracle Java 您需要设置以下系统属性 以便MQ JMS 类 能够使用正确的映射Oracle CipherSuite 名称:
System.setProperty("com.ibm.mq.cfg.useIBMCipherMappings", "false");
您提供的错误似乎来自 IBM i MQ 队列管理器,但没有提供足够的信息来诊断问题。
错误说明如下:
An unexpected SSL communications error occurred for a channel, as reported in the preceding messages.
您能否编辑您的问题并提供 "preceding messages" 中的详细信息。
你说
for some reason my application is revoking the certificate (something related with CLR)
可能是 MQ 队列管理器正在尝试自己连接到在您的客户端证书的 AuthorityInfoAccess (AIA) 证书扩展中指定的 OCSP 服务器。如果 MQ 无法使用默认配置访问此 OCSP 服务器,则连接将被拒绝。如果您无法更新您的网络以允许连接到 OCSP 服务器,那么您可以禁用此检查,但请注意,您将不知道证书是否被吊销。要禁用检查,可以将以下内容添加到队列管理器的 qm.ini
文件 SSL
节:
SSL:
OCSPAuthentication=Optional
OCSPCheckExtensions=no
最后一条评论,您在示例代码 TLS_RSA_WITH_AES_256_CBC_SHA
中列出的 CipherSuite 是 TLS1.0 CipherSuite。与之前的 SSL 一样,TLS1.1 和 TLS1.1 通常在许多行业中被弃用。我查找了对 post 的参考,“tls 1.0 end of life” 的 google 给出了很多参考。
下面引用一个“TLS 1.0 end-of-life on June 30th, 2018”:
The Deadline The PCI Council is responsible for determining when older protocols are phased out. They initially decided TLS 1.0 would
reach end of life on 30 June 2016, later extending the date to 30 June
2018. The final deadline has past and all web servers, web browsers, web software and email applications have had to discontinue TLS 1.0
support, or fall foul of important security updates.
我建议选择上面链接的知识中心页面中列为 TLS1.2 的一个,例如 TLS_RSA_WITH_AES_256_CBC_SHA256
。
我正在尝试使用 IBM MQ(版本 8.0.0.8)和 SSL 使用 Java 客户端,基本上构建于:
- Oracle JKD 8 和 IBM JRE 7(出于测试目的,我为它们各自准备了一个客户端)
- com.ibm.mq.allclient-9.1.0.0.jar
- javax.jms-api-2.0.1.jarspring-jms-4.3.7.RELEASE.jar
- spring-jms-4.3.7.RELEASE.jar
MQ 是 Request/Reply 类型。
我有正确的证书并设置了所有 MQ 属性,但由于某种原因连接“断开”并且我在客户端没有收到任何错误并且我的请求从未得到任何响应并保持 运行“永远”,从未得到任何回应。我唯一的线索是 MQ 日志中的一条错误消息:
Process(31600.16) User(QMQM) Jobname(JOB_NAME) Host(HOST_NAME) VRMF(8.0.0.8) QMgr(MANAGER_NAME) AMQ9638: SSL communications error for channel 'CHANNEL_NAME'. EXPLANATION: Cause . . . . . : An unexpected SSL communications error occurred for a channel, as reported in the preceding messages. The channel is 'CHANNEL_NAME';
奇怪的是发生了 SSL 握手,我的证书被 MQ 接受了,但由于某种原因在此之后发生了一些事情。我正在尝试同时使用 Oracle JRE 8 和 IBM JRE 7。可能是 MQ 方面的东西 (IBM MQ v8.0.0.8) 或我这边缺少的某些配置。
我已经安装了 JCE Unlimited Policies,所以问题不是 CipherSpec X CipherSuite。
我正在使用 -Djavax.net.debug=all 并且我可以看到我的证书被正确使用并且我看不到任何问题...
我在 MQ 团队的联系人告诉我,出于某种原因,我的应用程序正在吊销证书(与 CLR 相关),但我不知道为什么会发生这种情况。
我的Java代码:
public Message callMQ() {
Message message = null;
try {
MQConnectionFactory factory = mqQueueConnectionFactory();
JMSContext context = factory.createContext();
Destination requestQueue = context.createQueue("queue:///REQUEST_QUEUE");
Destination replyQueue = context.createQueue("queue:///REPLY_QUEUE");
JmsTemplate jmsTemplate = new JmsTemplate(factory);
FIXMLRootInbound inbound = new FIXMLRootInbound();
String xml = XmlUtil.xmlObjectToString(inbound);
message = jmsTemplate.sendAndReceive(requestQueue,
session -> {
Message req = session.createTextMessage(xml);
req.setJMSCorrelationID(UUID.randomUUID().toString());
req.setJMSDestination(requestQueue);
req.setJMSReplyTo(replyQueue);
return req;
});
} catch (Throwable e) {
e.printStackTrace();
}
return message;
}
private MQConnectionFactory mqQueueConnectionFactory() throws NoSuchAlgorithmException, KeyStoreException,
IOException, CertificateException, UnrecoverableKeyException, KeyManagementException, JmsException {
SSLSocketFactory sslSocketFactory = sslContext().getSocketFactory();
MQEnvironment.sslSocketFactory = sslSocketFactory;
MQEnvironment.sslCipherSuite = "TLS_RSA_WITH_AES_256_CBC_SHA";
MQEnvironment.sslFipsRequired = false;
MQConnectionFactory mqQueueConnectionFactory = new MQQueueConnectionFactory();
mqQueueConnectionFactory.setHostName(host);
try {
mqQueueConnectionFactory.setTransportType(JMSC.MQJMS_TP_CLIENT_MQ_TCPIP);
mqQueueConnectionFactory.setIntProperty(WMQConstants.WMQ_CONNECTION_MODE,
WMQConstants.WMQ_CM_CLIENT);
mqQueueConnectionFactory.setQueueManager(queueManager);
mqQueueConnectionFactory.setSSLCipherSuite("TLS_RSA_WITH_AES_256_CBC_SHA");
mqQueueConnectionFactory.setCCSID(285);
mqQueueConnectionFactory.setChannel(channel);
mqQueueConnectionFactory.setPort(port);
mqQueueConnectionFactory.setSSLSocketFactory(sslSocketFactory);
mqQueueConnectionFactory.setSSLFipsRequired(false);
} catch (Exception e) {
log.error("Error creating MQQueueConnectionFactory.", e);
}
return mqQueueConnectionFactory;
}
private SSLContext sslContext() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
try (InputStream cert = new FileInputStream("C:\myplace\Dev\Certificates\MY_KEYSTORE.jks")) {
final KeyStore caCertsKeyStore = KeyStore.getInstance("JKS");
caCertsKeyStore.load(cert, "changeit".toCharArray());
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX");
PKIXRevocationChecker rc = (PKIXRevocationChecker)cpb.getRevocationChecker();
rc.setOptions(EnumSet.of(
PKIXRevocationChecker.Option.PREFER_CRLS,
PKIXRevocationChecker.Option.ONLY_END_ENTITY,
PKIXRevocationChecker.Option.SOFT_FAIL,
PKIXRevocationChecker.Option.NO_FALLBACK));
PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(caCertsKeyStore, new X509CertSelector());
pkixParams.addCertPathChecker(rc);
kmf.init(caCertsKeyStore, "changeit".toCharArray());
tmf.init( new CertPathTrustManagerParameters(pkixParams) );
final SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
return sslContext;
} catch (Exception e) {
throw new RuntimeException("Exception creating SSLContext", e);
}
}
由于您使用的是 9.1.0.0 com.ibm.mq.allclient.jar
,因此您不需要所有与密钥库相关的代码,例如:
SSLSocketFactory sslSocketFactory = sslContext().getSocketFactory();
//Note that MQEnvironment is used with IBM MQ Classes for Java not IBM MQ Classes for JMS
MQEnvironment.sslSocketFactory = sslSocketFactory;
MQEnvironment.sslCipherSuite = "TLS_RSA_WITH_AES_256_CBC_SHA";
MQEnvironment.sslFipsRequired = false;
mqQueueConnectionFactory.setSSLSocketFactory(sslSocketFactory);
private SSLContext sslContext() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
try (InputStream cert = new FileInputStream("C:\myplace\Dev\Certificates\MY_KEYSTORE.jks")) {
final KeyStore caCertsKeyStore = KeyStore.getInstance("JKS");
caCertsKeyStore.load(cert, "changeit".toCharArray());
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX");
PKIXRevocationChecker rc = (PKIXRevocationChecker)cpb.getRevocationChecker();
rc.setOptions(EnumSet.of(
PKIXRevocationChecker.Option.PREFER_CRLS,
PKIXRevocationChecker.Option.ONLY_END_ENTITY,
PKIXRevocationChecker.Option.SOFT_FAIL,
PKIXRevocationChecker.Option.NO_FALLBACK));
PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(caCertsKeyStore, new X509CertSelector());
pkixParams.addCertPathChecker(rc);
kmf.init(caCertsKeyStore, "changeit".toCharArray());
tmf.init( new CertPathTrustManagerParameters(pkixParams) );
final SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
return sslContext;
} catch (Exception e) {
throw new RuntimeException("Exception creating SSLContext", e);
}
}
您可以将其替换为设置以下两个系统属性,这将适用于 Oracle 和 IBM Java:
System.setProperty("javax.net.ssl.keyStore", "C:\myplace\Dev\Certificates\MY_KEYSTORE.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "changeit");
以上设置一直适用于 IBM Java,但对于 Oracle Java 这不适用于旧版本的 MQ。在以下 IBM MQ 版本中针对 Oracle java 进行了修复(Base 9.0 和 9.1 具有相同的修复):
Version Maintenance Level v7.1 7.1.0.8 v7.5 7.5.0.7 v8.0 8.0.0.5
IBM Java 和 Oracle Java 具有不同的 CipherSuite 名称,这些名称记录在 IBM MQ v9.1 知识中心页面“TLS CipherSpecs and CipherSuites in IBM MQ classes for JMS.
您在 posted 代码中指定了 TLS_RSA_WITH_AES_256_CBC_SHA
,这将是 MQ 队列管理器 SVRCONN
通道上的 SSLCIPH
值,并将映射到以下 CipherSuites:
- IBM Java:
SSL_RSA_WITH_AES_256_CBC_SHA
- 甲骨文 Java:
TLS_RSA_WITH_AES_256_CBC_SHA
与上述相关,如果您使用的是Oracle Java 您需要设置以下系统属性 以便MQ JMS 类 能够使用正确的映射Oracle CipherSuite 名称:
System.setProperty("com.ibm.mq.cfg.useIBMCipherMappings", "false");
您提供的错误似乎来自 IBM i MQ 队列管理器,但没有提供足够的信息来诊断问题。
错误说明如下:
An unexpected SSL communications error occurred for a channel, as reported in the preceding messages.
您能否编辑您的问题并提供 "preceding messages" 中的详细信息。
你说
for some reason my application is revoking the certificate (something related with CLR)
可能是 MQ 队列管理器正在尝试自己连接到在您的客户端证书的 AuthorityInfoAccess (AIA) 证书扩展中指定的 OCSP 服务器。如果 MQ 无法使用默认配置访问此 OCSP 服务器,则连接将被拒绝。如果您无法更新您的网络以允许连接到 OCSP 服务器,那么您可以禁用此检查,但请注意,您将不知道证书是否被吊销。要禁用检查,可以将以下内容添加到队列管理器的 qm.ini
文件 SSL
节:
SSL:
OCSPAuthentication=Optional
OCSPCheckExtensions=no
最后一条评论,您在示例代码 TLS_RSA_WITH_AES_256_CBC_SHA
中列出的 CipherSuite 是 TLS1.0 CipherSuite。与之前的 SSL 一样,TLS1.1 和 TLS1.1 通常在许多行业中被弃用。我查找了对 post 的参考,“tls 1.0 end of life” 的 google 给出了很多参考。
下面引用一个“TLS 1.0 end-of-life on June 30th, 2018”:
The Deadline The PCI Council is responsible for determining when older protocols are phased out. They initially decided TLS 1.0 would reach end of life on 30 June 2016, later extending the date to 30 June 2018. The final deadline has past and all web servers, web browsers, web software and email applications have had to discontinue TLS 1.0 support, or fall foul of important security updates.
我建议选择上面链接的知识中心页面中列为 TLS1.2 的一个,例如 TLS_RSA_WITH_AES_256_CBC_SHA256
。