Spring Configuration for JMS (Websphere MQ - SSL, Tomcat, JNDI, Non IBM JRE)

后端 未结 1 1679
余生分开走
余生分开走 2021-02-02 16:43

Background: I have a relatively old application that uses Websphere MQ for messaging. It runs on WAS (Websphere Application Server) and uses MDBs (Message Drive

相关标签:
1条回答
  • 2021-02-02 17:24

    I think I finally figured out how to pull this off... here's a brief description of the steps. If you need more details let me know.

    Pre-reqs: Websphere MQ Server installed (at least v 8.0.0.2) Configure the QM, SSL and non-SSL channels, create Qs and all that good stuff you need. Needless to say, you need the Websphere MQ jars. Be mindful of any licensing restrictions.

    Step 1: Get the direct connection working with no SSL, no JNDI. You will need to use these beans to configure your spring based JMS listeners and JMS Templates etc.

    <bean id="mq-jms-cf-sandbox"
       class="org.springframework.jms.connection.SingleConnectionFactory">
        <property name="targetConnectionFactory">
        <ref bean="mqQueueConnectionFactory" />
        </property>
    </bean>
    <bean id="mqQueueConnectionFactory" class="com.ibm.mq.jms.MQQueueConnectionFactory">
       <property name="hostName" value="localhost" />
       <property name="port" value="1414" />
       <property name="queueManager" value="QM_SANDBOX" />
       <property name="transportType" value="1" />
       <property name="channel" value="NON_SSL_CHANNEL" />
     </bean>
     <bean id="jms-destination-sandbox" class="com.ibm.mq.jms.MQQueue">
       <constructor-arg value="SANDBOX_Q" />
        <property name="baseQueueManagerName">
        <value>QM_SANDBOX</value>
        </property>
        <property name="baseQueueName">
        <value>SANDBOX_Q</value>
        </property>
    </bean>
    

    Step 2: Get the direct connection working with SSL, no JNDI. I found setting this up a little tricky.

    2a. Since I was using a non-IBM JRE, I had to make sure the cipher specs & cipher suites needed to be configured according to the mappings specified here: http://www-01.ibm.com/support/docview.wss?uid=swg1IV66840

    This obviously means that we at least have to have our Websphere MQ upgraded to 8.0.0.2. In my case I used ECDHE_RSA_AES_256_GCM_SHA384 on the SSL channel and configured the jms beans within application to use TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, like so:

    <bean id="mq-jms-cf-sandbox"
        class="org.springframework.jms.connection.SingleConnectionFactory">
        <property name="targetConnectionFactory">
            <ref bean="mqQueueConnectionFactory" />
        </property>
    </bean>
    <bean id="mqQueueConnectionFactory" class="com.ibm.mq.jms.MQQueueConnectionFactory">
      <property name="hostName" value="localhost" />
      <property name="port" value="1414" />
      <property name="queueManager" value="QM_SANDBOX" />
      <property name="transportType" value="1" />
      <property name="channel" value="SSL_CHANNEL" />
      <property name="SSLCipherSuite" value="TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"/>
    </bean>
    <bean id="jms-destination-sandbox" class="com.ibm.mq.jms.MQQueue">
    <constructor-arg value="SANDBOX_Q" />
        <property name="baseQueueManagerName">
           <value>QM_SANDBOX</value>
        </property>
        <property name="baseQueueName">
           <value>SANDBOX_Q</value>
        </property>
    </bean>
    

    2b. Create certs, keystores (kdbs), exchange certs etc. There are many ways to do this. But be mindful that you will need to stash passwords, the key label for the queue manager must be ‘ibmwebspheremqqmgr’ – all in lower case, no spaces, (without quotes), the key label must be like ‘ibmwebspheremquserid’ – all in lower case, no spaces, (without quotes) where userid is the userid that runs tomcat. If you need more details on exactly how I did it using self signed certs, let me know.

    2c. Now you have to get the JVM that tomcat runs, to read your keystores. There are many ways but here's how I did it: Create a setenv.bat file in the tomcat bin folder, with the following contents (debugging SSL is optional)

    set JAVA_OPTS="-Djavax.net.ssl.trustStore=C:\path-to-keystore\key.jks" "-Djavax.net.ssl.trustStorePassword=topsecret" "-Djavax.net.ssl.keyStore=C:\path-to-keystore\key.jks" "-Djavax.net.ssl.keyStorePassword=topsecret" "-Djavax.net.debug=ssl" "-Dcom.ibm.mq.cfg.useIBMCipherMappings=false"
    

    2d. Start tomcat using the following command:

    catalina.bat run > ..\logs\tomcat.log 2>&1 
    

    To stop, just press ctrl+c (on windows). Whichever way you do it, make sure that setenv.bat is used during start up. Or use JAVA_OPTS to set the keystore properties.

    2e. Verify that the using the SSL channel works.

    Step 3: Get a JNDI connection working with non-SSL, JNDI There are many was to set up JNDI on tomcat. Here's how I did it: Within the web application create a file META-INF/Context.xml with the following contents:

    <Resource
       name="jms/qcf_sandbox"
       auth="Container"
       type="com.ibm.mq.jms.MQQueueConnectionFactory"
       factory="com.ibm.mq.jms.MQQueueConnectionFactoryFactory"
       description="JMS Queue Connection Factory for sending messages"
       HOST="localhost"
       PORT="1414"
       CHAN="NON_SSL_CHANNEL"
       TRAN="1"
       QMGR="QM_SANDBOX"/>
    <Resource
       name="jms/SandboxQ"
       auth="Container"
       type="com.ibm.mq.jms.MQQueue"
       factory="com.ibm.mq.jms.MQQueueFactory"
       description="JMS Queue"
       QU="SANDBOX_Q"/>
    

    Now in your spring config, instead of the direct configurations, all you have to do is:

    <jee:jndi-lookup id="mq-jms-cf-sandbox" jndi-name="java:/comp/env/jms/qcf_sandbox" resource-ref="false" />
    <jee:jndi-lookup id="jms-destination-sandbox" jndi-name="java:/comp/env/jms/SandboxQ" resource-ref="false" />
    

    Note that for brevity, I just didn't use resource references. In case you do, there a few additional steps which are straight forward.

    Step 4: Now the final step is to use an SSL channel and JNDI. Assuming you have done step 2, this is easy. Modify the META-INF/Context.xml with the following contents:

    <Resource
       name="jms/qcf_sandbox"
       auth="Container"
       type="com.ibm.mq.jms.MQQueueConnectionFactory"
       factory="com.ibm.mq.jms.MQQueueConnectionFactoryFactory"
       description="JMS Queue Connection Factory for sending messages"
       HOST="localhost"
       PORT="1414"
       CHAN="SSL_CHANNEL"
       TRAN="1"
       QMGR="QM_SANDBOX"
       SCPHS="TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"/>
    <Resource
       name="jms/SandboxQ"
       auth="Container"
       type="com.ibm.mq.jms.MQQueue"
       factory="com.ibm.mq.jms.MQQueueFactory"
       description="JMS Queue"
       QU="SANDBOX_Q"/>
    

    Note the line with SCPHS="TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384". If you need to set other such parameters, see the "Short Form" column in this link: https://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.ref.dev.doc/q111800_.htm%23jm10910_?lang=en

    Hopefully all this works for you. Good luck!

    Once this configuration works, sending messages is pretty straight forward. But this is how you can listen for a message on a queue using Spring JMS Reference: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/jms.html

    Step 1: Use Spring's DefaultMessageListenerContainer and configure your beans in an xml file like so (spring-beans.xml):

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"      
        "http://www.springframework.org/dtd/spring-beans.dtd">
    
       <!-- this is the Message Driven POJO (MDP) -->
       <bean id="messageListener" class="jmsexample.ExampleListener" />
    
       <!-- and this is the message listener container -->
       <bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
           <property name="connectionFactory" ref="mq-jms-cf-sandbox"/>
           <property name="destination" ref="jms-destination-sandbox"/>
           <property name="messageListener" ref="messageListener" />
       </bean>
    
    </beans>
    

    Step 2: Add this to your web.xml

    <context-param>
       <param-name>contextConfigLocation</param-name>
       <param-value>/WEB-INF/context/spring-beans.xml</param-value>
     </context-param>
    
    <listener>
           <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    

    Step 3: Write a Message Listener class like so:

    import javax.jms.JMSException;
    import javax.jms.Message;
    import javax.jms.MessageListener;
    import javax.jms.TextMessage;
    
    public class ExampleListener implements MessageListener {
    
        public void onMessage(Message message) {
            if (message instanceof TextMessage) {
                try {
                    System.out.println(((TextMessage) message).getText());
                }
                catch (JMSException ex) {
                    throw new RuntimeException(ex);
                }
            }
            else {
                throw new IllegalArgumentException("Message must be of type TextMessage");
            }
        }
    }
    

    Alternatively, instead of step 3, if you are using spring integration, you can do something like so:

    <int:channel id="jms-inbound"/>
        <int-jms:message-driven-channel-adapter
            id="jms-inbound-adapter" container="jmsContainer" channel="jms-inbound" 
            extract-payload="true" acknowledge="transacted" 
            message-converter="messagingMessageConverter" />
    
    <beans:bean id="messagingMessageConverter" class="org.springframework.jms.support.converter.MessagingMessageConverter">
        </beans:bean>
    
    0 讨论(0)
提交回复
热议问题