web-dev-qa-db-fra.com

Configuration de Spring pour JMS (Websphere MQ - SSL, Tomcat, JNDI, non IBM JRE)

Contexte: J'ai une application relativement ancienne qui utilise Websphere MQ pour la messagerie. Il s'exécute sur WAS (Websphere Application Server) et utilise des MDB (Message Driven Beans). J'ai réussi à remplacer toutes les MDB à l'aide de Spring Integration - JMS . Ma prochaine étape est d'essayer de voir si je peux le porter hors de WAS afin qu'il puisse s'exécuter sur n'importe quel autre conteneur de servlet avec un JRE non IBM (j'essaie: Apache Tomcat). Notez que la sécurisation des canaux à l'aide de SSL est une exigence. Je préfère utiliser JNDI.

Objectif final: Découpler mon application du serveur d'applications (WAS) et d'autres infrastructures comme la messagerie (MQ). Mais retirer ceci de WAS sur Tomcat est la première étape. Vient ensuite la tâche de mettre à jour mon infrastructure de messagerie avec quelque chose de plus évolutif. Cela me permet de mettre à jour les composants individuels de l'infrastructure sur lesquels mon application s'appuie, une chose à la fois (serveur d'applications, couche de messagerie, magasin de données) sans trop perturber mon application au fur et à mesure.

Question: Maintenant, mon défi est de définir des ressources JNDI sur Tomcat pouvant accéder à Websphere MQ. J'ai fait des progrès à ce sujet en utilisant des canaux non SSL que j'ai définis dans le fichier context.xml comme ceci:

<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="CHANNEL_SANDBOX"
   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"/>

Ma prochaine étape consiste à faire fonctionner cela avec les canaux SSL. Je comprends la partie qui implique la mise en place des keystores (fichiers kdb et génération et échange de certificats), la configuration des canaux SSL sur le QM, etc. J'ai déjà tout ça en marche. Comment faire en sorte que Tomcat utilise mon magasin de clés, ma suite de chiffrement, etc.? Des pointeurs ou un exemple de travail serait formidable!

Remarque: J'utilise actuellement Spring Integration 4.2, Websphere MQ v8, Tomcat v9.

Je dois ajouter que j'ai tout essayé sans JNDI en premier. Voici donc ma configuration Spring JMS non SSL sans JNDI, qui fonctionne:

<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="CHANNEL_SANDBOX" />
 </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> 
13
code4kix

Je pense que j'ai enfin compris comment retirer cela ... voici une brève description des étapes. Si vous avez besoin de plus de détails, faites-le moi savoir.

Pré-requis : Websphere MQ Server installé (au moins v 8.0.0.2) Configurer les canaux QM, SSL et non SSL, créer des Qs et tout ce qui bonnes choses dont vous avez besoin. Inutile de dire que vous avez besoin des bocaux Websphere MQ. Soyez conscient de toute restriction de licence.

Étape 1 : faites fonctionner la connexion directe sans SSL ni JNDI. Vous devrez utiliser ces beans pour configurer vos écouteurs JMS à ressort et vos modèles JMS, 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>

Étape 2 : Obtenez la connexion directe avec SSL, pas JNDI. J'ai trouvé la configuration un peu délicate.

2a . Puisque j'utilisais un JRE non IBM, je devais m'assurer que les spécifications de chiffrement et les suites de chiffrement devaient être configurées selon les mappages spécifiés ici: http://www-01.ibm.com/support/ docview.wss? uid = swg1IV6684

Cela signifie évidemment que nous devons au moins avoir notre Websphere MQ mis à niveau vers 8.0.0.2. Dans mon cas, j'ai utilisé ECDHE_RSA_AES_256_GCM_SHA384 sur le canal SSL et configuré les beans jms dans l'application pour utiliser TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, comme ceci:

<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 . Créez des certificats, des magasins de clés (kdbs), échangez des certificats, etc. Il existe plusieurs façons de procéder. Mais gardez à l'esprit que vous devrez cacher les mots de passe, le libellé de clé du gestionnaire de files d'attente doit être 'ibmwebspheremq qmgr' - tout en minuscules, sans espace, (sans guillemets), le libellé de clé doit être comme 'ibmwebspheremq userid' - tout en minuscules, sans espace, (sans guillemets) où userid est l'ID utilisateur qui exécute Tomcat. Si vous avez besoin de plus de détails sur la façon dont je l'ai fait en utilisant des certificats auto-signés, faites-le moi savoir.

2c . Vous devez maintenant obtenir la machine virtuelle Java exécutée par Tomcat pour lire vos fichiers de clés. Il y a plusieurs façons mais voici comment je l'ai fait: Créez un fichier setenv.bat dans le dossier bin Tomcat, avec le contenu suivant (le débogage SSL est facultatif)

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 . Démarrez Tomcat à l'aide de la commande suivante:

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

Pour arrêter, appuyez simplement sur ctrl + c (sous Windows). Quelle que soit la façon dont vous le faites, assurez-vous que setenv.bat est utilisé lors du démarrage. Ou utilisez Java_OPTS pour définir les propriétés du magasin de clés.

2e . Vérifiez que l'utilisation du canal SSL fonctionne.

Étape 3 : Obtenez une connexion JNDI fonctionnant avec non SSL, JNDI Il y en a beaucoup pour configurer JNDI sur Tomcat. Voici comment je l'ai fait: dans l'application Web, créez un fichier META-INF/Context.xml avec le contenu suivant:

<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"/>

Maintenant, dans votre configuration de printemps, au lieu des configurations directes, tout ce que vous avez à faire est de:

<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" />

Notez que par souci de concision, je n'ai tout simplement pas utilisé de références de ressources. Au cas où vous le feriez, il y a quelques étapes supplémentaires qui sont simples.

Étape 4 : La dernière étape consiste maintenant à utiliser un canal SSL et JNDI. En supposant que vous avez effectué l'étape 2, c'est facile. Modifiez le META-INF/Context.xml avec le contenu suivant:

<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"/>

Notez la ligne avec SCPHS = "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384". Si vous devez définir d'autres paramètres de ce type, consultez la colonne "Short Form" de ce lien: https://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.ref. dev.doc/q111800_.htm% 23jm10910_? lang = en

J'espère que tout cela fonctionne pour vous. Bonne chance!

Une fois que cette configuration fonctionne, l'envoi de messages est assez simple. Mais voici comment vous pouvez écouter un message dans une file d'attente à l'aide de la référence Spring JMS: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/jms.html

Étape 1 : utilisez DefaultMessageListenerContainer de Spring et configurez vos beans dans un fichier xml comme cela (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>

Étape 2 : Ajoutez ceci à votre 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>

Étape 3 : Écrivez une classe d'écouteur de message comme ceci:

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");
        }
    }
}

Alternativement, au lieu de l'étape 3, si vous utilisez l'intégration Spring, vous pouvez faire quelque chose comme ceci:

<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>
12
code4kix