无法使用 Spring Boot 连接到具有多个 SSL 证书的多个 IBM MQ 通道

Posted

技术标签:

【中文标题】无法使用 Spring Boot 连接到具有多个 SSL 证书的多个 IBM MQ 通道【英文标题】:Not able to connect to multiple IBM MQ channels with multiple SSL certs using Spring Boot 【发布时间】:2021-11-25 07:15:44 【问题描述】:

无法连接到具有多个SSL 证书和Spring Boot 的多个IBM MQ 通道

pom.xml

<dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>sslcontext-kickstart</artifactId>
    <version>6.6.0</version>
</dependency>
<dependency>
    <groupId>com.ibm.mq</groupId>
    <artifactId>mq-jms-spring-boot-starter</artifactId>
    <version>2.0.8</version>
</dependency>

注意:使用 sslcontext-kickstart api 和更多文档可以在这里找到

https://github.com/Hakky54/sslcontext-kickstart

MulticertApplication.java

public class MulticertApplication implements CommandLineRunner 

    @Autowired
    private SSLContextService sslContextService;

    public static void main(String[] args) 
        SpringApplication.run(MulticertApplication.class, args);
    

    @Override
    public void run(String... args) throws JMSException 
        // Both WORKS !!
        testConnectionWithIndividualSSL("APP1");
        log.info("APP1 CONNECTION SUCCESS WITH INDIVIDUAL SSL !!");
        testConnectionWithIndividualSSL("APP2");
        log.info("APP2 CONNECTION SUCCESS WITH INDIVIDUAL SSL !!");

        // Does NOT WORK !!
        SSLSocketFactory sslSocketFactory = sslContextService.getCombinedSSLSocketFactory();
        testConnectionWithCombinedSSL(sslSocketFactory, "APP1");
        log.info("APP1 CONNECTION SUCCESS WITH COMBINED SSL !!");
        testConnectionWithCombinedSSL(sslSocketFactory, "APP2");
        log.info("APP2 CONNECTION SUCCESS WITH COMBINED SSL !!");
    

    private void testConnectionWithIndividualSSL(String app) throws JMSException 
        MQQueueConnectionFactory mqQueueConnectionFactory = new MQQueueConnectionFactory();
        mqQueueConnectionFactory.setHostName("MQHOST.company.net");
        mqQueueConnectionFactory.setPort(1414);
        mqQueueConnectionFactory.setTransportType(1);
        mqQueueConnectionFactory.setQueueManager("MQHOST");
        if (app.equalsIgnoreCase("APP1")) 
            mqQueueConnectionFactory.setChannel("APP1.SVRCONN.TLS");
         else 
            mqQueueConnectionFactory.setChannel("APP2.SVRCONN.TLS");
        
        mqQueueConnectionFactory.setSSLCipherSuite("TLS_RSA_WITH_AES_256_CBC_SHA256");
        mqQueueConnectionFactory.setSSLFipsRequired(false);
        mqQueueConnectionFactory.setSSLSocketFactory(sslContextService.getSSLSocketFactory(app));
        MQQueueConnection mqQueueConnection = (MQQueueConnection) mqQueueConnectionFactory.createQueueConnection();
        mqQueueConnection.start();
        mqQueueConnection.stop();
        mqQueueConnection.close();
    

    private void testConnectionWithCombinedSSL(SSLSocketFactory sslSocketFactory, String app) throws JMSException 
        MQQueueConnectionFactory mqQueueConnectionFactory = new MQQueueConnectionFactory();
        mqQueueConnectionFactory.setHostName("MQHOST.company.net");
        mqQueueConnectionFactory.setPort(1414);
        mqQueueConnectionFactory.setTransportType(1);
        mqQueueConnectionFactory.setQueueManager("MQHOST");
        if (app.equalsIgnoreCase("APP1")) 
            mqQueueConnectionFactory.setChannel("APP1.SVRCONN.TLS");
         else 
            mqQueueConnectionFactory.setChannel("APP2.SVRCONN.TLS");
        
        mqQueueConnectionFactory.setSSLCipherSuite("TLS_RSA_WITH_AES_256_CBC_SHA256");
        mqQueueConnectionFactory.setSSLFipsRequired(false);
        mqQueueConnectionFactory.setSSLSocketFactory(sslSocketFactory);
        MQQueueConnection mqQueueConnection = (MQQueueConnection) mqQueueConnectionFactory.createQueueConnection();
        mqQueueConnection.start();
        mqQueueConnection.stop();
        mqQueueConnection.close();
    


SSLContextService.java

@Service 
public class SSLContextService 

    private static final String APP1_JKS_PWD = "abc";
    private static final String APP2_JKS_PWD = "def";

    public SSLSocketFactory getSSLSocketFactory(String app) 
        log.info("app: " + app);
        if (app.equalsIgnoreCase("APP1")) 
            return SSLFactory.builder()
                    .withIdentityMaterial("app1.jks", APP1_JKS_PWD.toCharArray())
                    .withTrustMaterial("app1.jks", APP1_JKS_PWD.toCharArray())
                    .build().getSslContext().getSocketFactory();
         else if (app.equalsIgnoreCase("APP2")) 
            return SSLFactory.builder()
                    .withIdentityMaterial("app2.jks", APP2_JKS_PWD.toCharArray())
                    .withTrustMaterial("app2.jks", APP2_JKS_PWD.toCharArray())
                    .build().getSslContext().getSocketFactory();
        
        return null;
    
    
    public SSLSocketFactory getCombinedSSLSocketFactory() 
        return SSLFactory.builder()
                .withIdentityMaterial("app1.jks", APP1_JKS_PWD.toCharArray())
                .withIdentityMaterial("app2.jks", APP2_JKS_PWD.toCharArray())
                .withTrustMaterial("app1.jks", APP1_JKS_PWD.toCharArray())
                .withTrustMaterial("app2.jks", APP2_JKS_PWD.toCharArray())
                .build().getSslContext().getSocketFactory();
    


错误日志:

2021-09-30 17:55:51.892  INFO 62988 --- [  restartedMain] c.e.multicert.MulticertApplication      : Started MulticertApplication in 3.414 seconds (JVM running for 4.448)
2021-09-30 17:55:51.899  INFO 62988 --- [  restartedMain] c.e.multicert.service.SSLContextService  : app: APP1
2021-09-30 17:55:52.513  INFO 62988 --- [  restartedMain] c.e.multicert.MulticertApplication      : APP1 CONNECTION SUCCESS WITH INDIVIDUAL SSL !!
2021-09-30 17:55:52.514  INFO 62988 --- [  restartedMain] c.e.multicert.service.SSLContextService  : app: APP2
2021-09-30 17:55:52.555  INFO 62988 --- [  restartedMain] c.e.multicert.MulticertApplication      : APP2 CONNECTION SUCCESS WITH INDIVIDUAL SSL !!
2021-09-30 17:55:52.597  INFO 62988 --- [  restartedMain] c.e.multicert.MulticertApplication      : APP1 CONNECTION SUCCESS WITH COMBINED SSL !!
2021-09-30 17:55:52.641  INFO 62988 --- [  restartedMain] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2021-09-30 17:55:52.663 ERROR 62988 --- [  restartedMain] o.s.boot.SpringApplication               : Application run failed

java.lang.IllegalStateException: Failed to execute CommandLineRunner
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:807) [spring-boot-2.4.2.jar:2.4.2]
    at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:788) [spring-boot-2.4.2.jar:2.4.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:333) [spring-boot-2.4.2.jar:2.4.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1311) [spring-boot-2.4.2.jar:2.4.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1300) [spring-boot-2.4.2.jar:2.4.2]
    at com.example.multicert.MulticertApplication.main(MulticertApplication.java:23) [classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_181]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_181]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_181]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_181]
    at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) [spring-boot-devtools-2.4.2.jar:2.4.2]
Caused by: com.ibm.msg.client.jms.DetailedJMSSecurityException: JMSWMQ2013: The security authentication was not valid that was supplied for QueueManager 'MQHOST' with connection mode 'Client' and host name 'MQHOST.company.net(1414)'.
    at com.ibm.msg.client.wmq.common.internal.Reason.reasonToException(Reason.java:531) ~[com.ibm.mq.allclient-9.1.0.0.jar:9.1.0.0 - p910-L180705]
    at com.ibm.msg.client.wmq.common.internal.Reason.createException(Reason.java:215) ~[com.ibm.mq.allclient-9.1.0.0.jar:9.1.0.0 - p910-L180705]
    at com.ibm.msg.client.wmq.internal.WMQConnection.<init>(WMQConnection.java:424) ~[com.ibm.mq.allclient-9.1.0.0.jar:9.1.0.0 - p910-L180705]
    at com.ibm.msg.client.wmq.factories.WMQConnectionFactory.createV7ProviderConnection(WMQConnectionFactory.java:8475) ~[com.ibm.mq.allclient-9.1.0.0.jar:9.1.0.0 - p910-L180705]
    at com.ibm.msg.client.wmq.factories.WMQConnectionFactory.createProviderConnection(WMQConnectionFactory.java:7815) ~[com.ibm.mq.allclient-9.1.0.0.jar:9.1.0.0 - p910-L180705]
    at com.ibm.msg.client.jms.admin.JmsConnectionFactoryImpl._createConnection(JmsConnectionFactoryImpl.java:303) ~[com.ibm.mq.allclient-9.1.0.0.jar:9.1.0.0 - p910-L180705]
    at com.ibm.msg.client.jms.admin.JmsConnectionFactoryImpl.createConnection(JmsConnectionFactoryImpl.java:236) ~[com.ibm.mq.allclient-9.1.0.0.jar:9.1.0.0 - p910-L180705]
    at com.ibm.mq.jms.MQConnectionFactory.createCommonConnection(MQConnectionFactory.java:6016) ~[com.ibm.mq.allclient-9.1.0.0.jar:9.1.0.0 - p910-L180705]
    at com.ibm.mq.jms.MQQueueConnectionFactory.createQueueConnection(MQQueueConnectionFactory.java:111) ~[com.ibm.mq.allclient-9.1.0.0.jar:9.1.0.0 - p910-L180705]
    at com.example.multicert.MulticertApplication.testConnectionWithCombinedSSL(MulticertApplication.java:76) [classes/:na]
    at com.example.multicert.MulticertApplication.run(MulticertApplication.java:38) [classes/:na]
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:804) [spring-boot-2.4.2.jar:2.4.2]
    ... 10 common frames omitted
Caused by: com.ibm.mq.MQException: JMSCMQ0001: IBM MQ call failed with compcode '2' ('MQCC_FAILED') reason '2035' ('MQRC_NOT_AUTHORIZED').
    at com.ibm.msg.client.wmq.common.internal.Reason.createException(Reason.java:203) ~[com.ibm.mq.allclient-9.1.0.0.jar:9.1.0.0 - p910-L180705]
    ... 20 common frames omitted

【问题讨论】:

密钥库中的所有证书是由同一个问题还是不同的发行者签名的? 是的,同一发行人。第一个案例有效。这就是让我困惑的地方:) 您是否使用多个密钥库? 如果所有证书都在同一个密钥库中,则不确定 java 将使用哪个证书。它根据它知道远程服务器信任的证书进行选择,但如果所有证书都由同一个 CA 颁发,则多个证书是受信任的。我已经看到在这种情况下挑选的钥匙店中的第一个。您可能需要使用多个密钥库才能使用其他证书。如果它是一个多线程进程,您会遇到问题,因为指向密钥存储的 java 系统属性是全局的。我建议您使用单个证书或拆分,以便每个进程只有一个证书。 【参考方案1】:

看起来底层 KeyManager 无法将正确的客户端证书发送到 ibm mq 服务器。这将在使用具有相同密钥算法的多个密钥库时发生。在这种情况下,它只会获取第一个客户端证书。

所以我建议使用 SSLFactory 的附加选项来正确地将客户端证书路由到正确的 ibm mq 服务器。所以下面的设置应该可以解决问题:

public SSLSocketFactory getCombinedSSLSocketFactory() 
    return SSLFactory.builder()
            .withIdentityMaterial("app1.jks", APP1_JKS_PWD.toCharArray())
            .withIdentityMaterial("app2.jks", APP2_JKS_PWD.toCharArray())
            .withTrustMaterial("app1.jks", APP1_JKS_PWD.toCharArray())
            .withTrustMaterial("app2.jks", APP2_JKS_PWD.toCharArray())
            .withClientIdentityRoute("client-alias-one", "https://[MQHOST-ONE.company.net]:1414/")
            .withClientIdentityRoute("client-alias-two", "https://[MQHOST-TWO.company.net]:8463/")
            .build().getSslContext().getSocketFactory();

我从未在 ibm mq 上尝试过,所以我不确定这是否可行。所以这只是一个假设。使用传统设置(基本客户端和服务器通信),同时使用多个密钥库,它将起作用。所以我很好奇这个解决方案是否适合你。

对于withClientIdentityRoute,您需要将在密钥库文件中使用的证书的别名作为第一个参数传递。第二个参数应该是你的 ibm mq 服务器的 url。

请在此处详细阅读:Routing identity material to specific host

您可以尝试一下并在这里分享您的结果吗?

【讨论】:

好的,如何找到证书的别名?当我打开 .jks 文件时,它都是胡言乱语 是的,您可以在 jks 文件中找到它并使用它。如果这样更容易,您也可以在 jks 中重命名它 我的意思是,当我在 Notepad++ 中打开文件时,它都是不可读的。您是否建议或推荐任何其他方式或命令来找出别名? 啊,我明白了,你可以使用 KeyStore explorer 来查看 KeyStore 文件的内容,你可以从这里得到工具keystore-explorer.org 我的组织不允许在机器上进行任何下载。让我找到另一种方法来找出别名

以上是关于无法使用 Spring Boot 连接到具有多个 SSL 证书的多个 IBM MQ 通道的主要内容,如果未能解决你的问题,请参考以下文章