无法使用 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 通道的主要内容,如果未能解决你的问题,请参考以下文章

无法使用 Spring Boot 应用程序连接到 Docker 映像

Spring Boot 使用 JdbcTemplate 和多个数据源自动重新连接到 PostgreSQL

Spring Boot没有连接到RabbitMQ

无法使用spring boot连接到mysql数据库

在spring boot中连接到多个数据库

Docker 上的 Spring Boot 无法连接到 MySQL