将 Java 客户端 (JMS) 连接到 IBM MQ 时出现问题

Posted

技术标签:

【中文标题】将 Java 客户端 (JMS) 连接到 IBM MQ 时出现问题【英文标题】:Problem Connecting a Java Client (JMS) to a IBM MQ 【发布时间】:2019-03-17 11:04:06 【问题描述】:

我正在尝试使用带有 SSL 的 IBM MQ(版本 8.0.0.8),使用的 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 是请求/回复类型。

我设置了正确的证书和所有 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);
        
    

【问题讨论】:

【参考方案1】:

由于您使用的是 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”中。

您已在发布的代码中指定 TLS_RSA_WITH_AES_256_CBC_SHA,这将是 MQ 队列管理器 SVRCONN 通道上的 SSLCIPH 值,并将映射到以下 CipherSuites:

IBM Java:SSL_RSA_WITH_AES_256_CBC_SHA Oracle 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.

您能否编辑您的问题并提供“先前消息”中的详细信息。


你说

由于某种原因,我的应用程序正在吊销证书(与 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 一样,在许多行业中通常不推荐使用。我寻找帖子的参考,“tls 1.0 end of life”的谷歌提供了许多参考。

在下面引用一个“TLS 1.0 end-of-life on June 30th, 2018”:

截止日期 PCI 委员会负责确定何时淘汰旧协议。他们最初决定 TLS 1.0 将 2016 年 6 月 30 日达到使用寿命,之后将日期延长至 6 月 30 日 2018. 最后期限已经过去,所有网络服务器、网络浏览器、网络软件和电子邮件应用程序都不得不停止使用 TLS 1.0 支持或违反重要的安全更新。

我建议在上面链接到的知识中心页面中选择一个列为 TLS1.2 的,例如 TLS_RSA_WITH_AES_256_CBC_SHA256

【讨论】:

谢谢乔希!问题出在 MQ CipherSpec 中!

以上是关于将 Java 客户端 (JMS) 连接到 IBM MQ 时出现问题的主要内容,如果未能解决你的问题,请参考以下文章

将远程 JMS 客户端连接到 GlassFish 3

com.ibm.msg.client.jms.DetailedJMSException:JMSWMQ0018:无法使用连接模式“yyy”和主机名“zzz”连接到队列管理器“xxx”

将 JMS 客户端连接到 Apache Kafka

使用 MQClient java 和 MQExplorer 错误 JMSWMQ2013 将 JMS 连接到 MQ

在同一个应用程序中使用 ActiveMQ 5 和 IBM MQ 8

使用 Java 1.6 通过 JMS 连接到 SQS