dpurkait
dpurkait

Reputation: 33

Issue with Connecting IBM MQ Using Externalized User Credential - Spring Boot + JMS + IBM MQ 8.0.0.9

I need a help for the captioned subject. Request your suggestion here.

I am using spring boot framework to get the messages from IBM MQ 8.0.0.9. The attached program meets the basic expectation. However, It always uses the OS credential (in specifically Windows Login credential) to connect to the IBM MQ. But I would like it to use the credential this is given in property file. Even if, I intentionally give wrong credential in the property files, the program happily connects with the IBM MQ (using windows login credential which has access to the IBM MQ as well).

Here is the MQ Details - MQ Version 8.0.0.9, QM Version 8.0.0.9, CONNAUTH - Not Set, CHLAUTH - Disabled, QMGR CONNAUTH - Not Set, CHCKCLNT - OPTIONAL, AUTHINFO - SYS.DEFAULT.AUTHINFO.IDPWOS

Could you suggest what I am doing wrong here in the attach program. I have tried the commented line of code as well.

@EnableJms
@Configuration
@EnableTransactionManagement
public class JmsConfig {
    @Value("${ibm.mq.host}")
    private String host;
    @Value("${ibm.mq.port}")
    private Integer port;
    @Value("${ibm.mq.queueManager}")
    private String queueManager;
    @Value("${ibm.mq.channel}")
    private String channel;
    @Value("${ibm.mq.responseQueue}")
    private String responseQueue;
    @Value("${ibm.mq.userName}")
    private String userName;
    @Value("${ibm.mq.password}")
    private String password;
    @Value("${ibm.mq.receiveTimeout}")
    private long timeout;

    @Autowired(required=true)
    @Qualifier(value="responseListener")
    MessageListener  responseListener;

    @Bean (name="queueConnectionFactory")
    public MQQueueConnectionFactory mqQueueConnectionFactory() {
        MQQueueConnectionFactory mqQueueConnectionFactory = new MQQueueConnectionFactory();

        try {
            mqQueueConnectionFactory.setHostName(host);
            mqQueueConnectionFactory.setTransportType(WMQConstants.WMQ_CM_CLIENT);
            mqQueueConnectionFactory.setChannel(channel);
            mqQueueConnectionFactory.setPort(port);
            mqQueueConnectionFactory.setQueueManager(queueManager);
            mqQueueConnectionFactory.setCCSID(819);
            /*
            mqQueueConnectionFactory.setBooleanProperty(WMQConstants.USER_AUTHENTICATION_MQCSP, true);
            mqQueueConnectionFactory.setStringProperty(WMQConstants.USERID, userName);
            mqQueueConnectionFactory.setStringProperty(WMQConstants.PASSWORD, password);
            mqQueueConnectionFactory.setStringProperty(CMQC.USER_ID_PROPERTY, userName);
            mqQueueConnectionFactory.setStringProperty(CMQC.PASSWORD_PROPERTY, password);
            */
        } catch (JMSException e) {
            log.error("Error occured: " + e);
        }

        return mqQueueConnectionFactory;
    }

    @Primary
    @Bean (name="userCredentialsConnectionFactoryAdapter")
    @DependsOn(value = { "queueConnectionFactory" })
    UserCredentialsConnectionFactoryAdapter getUserCredentialsConnectionFactoryAdapter(
            MQQueueConnectionFactory mqQueueConnectionFactory) {
        UserCredentialsConnectionFactoryAdapter userCredentialsConnectionFactoryAdapter = new UserCredentialsConnectionFactoryAdapter();
        userCredentialsConnectionFactoryAdapter.setUsername(userName);
        userCredentialsConnectionFactoryAdapter.setPassword(password);
        userCredentialsConnectionFactoryAdapter.setTargetConnectionFactory(mqQueueConnectionFactory);
        return userCredentialsConnectionFactoryAdapter;
    }

    @Bean (name="simpleMessageListenerContainer")
    @DependsOn(value = { "userCredentialsConnectionFactoryAdapter" })
    public SimpleMessageListenerContainer queueResponseContainer(ConnectionFactory userCredentialsConnectionFactoryAdapter) {
        SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer();
        simpleMessageListenerContainer.setConnectionFactory(userCredentialsConnectionFactoryAdapter);
        simpleMessageListenerContainer.setConnectLazily(true);
        simpleMessageListenerContainer.setDestinationName(responseQueue);
        simpleMessageListenerContainer.setMessageListener(responseListener);
        return simpleMessageListenerContainer;
    }

    @Bean
    public JmsOperations jmsOperations(ConnectionFactory userCredentialsConnectionFactoryAdapter) {
        JmsTemplate jmsTemplate = new JmsTemplate(userCredentialsConnectionFactoryAdapter);
        jmsTemplate.setReceiveTimeout(timeout);
        return jmsTemplate;
    }
}

====================

@Component(value="responseListener")
public class ResponseListener implements MessageListener {

    public void onMessage(Message message) {    
        ...
    }
}

===================

@Component
public class ContainerChecker {

    @Autowired
    SimpleMessageListenerContainer  queueContainer;

    @Scheduled(fixedRate = 300000)
    public void reportContainerStatus() throws ServiceException{
        if(!queueContainer.isActive()) {
            ...
        } else {
            ...
        }
    }
}

Upvotes: 3

Views: 4098

Answers (1)

JoshMc
JoshMc

Reputation: 10672

Based on the updates in your comments:

  • Your application is using the IBM MQ classes for JMS from IBM MQ v8.0.0.9
  • Your application is connecting to a IBM MQ queue manager also running v8.0.0.9.
    • The queue manager has CONNAUTH disabled [QMGR CONNAUTH('')]
    • The queue manager has CHLAUTH disabled [`QMGR CHLAUTH(DISABLED)'].

In your configuration you show the following bean:

@Bean (name="userCredentialsConnectionFactoryAdapter")
@DependsOn(value = { "queueConnectionFactory" })
UserCredentialsConnectionFactoryAdapter getUserCredentialsConnectionFactoryAdapter(
        MQQueueConnectionFactory mqQueueConnectionFactory) {
    UserCredentialsConnectionFactoryAdapter userCredentialsConnectionFactoryAdapter = new UserCredentialsConnectionFactoryAdapter();
    userCredentialsConnectionFactoryAdapter.setUsername(userName);
    userCredentialsConnectionFactoryAdapter.setPassword(password);
    userCredentialsConnectionFactoryAdapter.setTargetConnectionFactory(mqQueueConnectionFactory);
    return userCredentialsConnectionFactoryAdapter;
}

You also have the following commented out code:

        /*
        mqQueueConnectionFactory.setBooleanProperty(WMQConstants.USER_AUTHENTICATION_MQCSP, true);
        mqQueueConnectionFactory.setStringProperty(WMQConstants.USERID, userName);
        mqQueueConnectionFactory.setStringProperty(WMQConstants.PASSWORD, password);
        mqQueueConnectionFactory.setStringProperty(CMQC.USER_ID_PROPERTY, userName);
        mqQueueConnectionFactory.setStringProperty(CMQC.PASSWORD_PROPERTY, password);
        */

With IBM MQ classes for JMS v8.0 and later the default for the setting WMQConstants.USER_AUTHENTICATION_MQCSP is FALSE. This denotes that the client should work in compatibility mode, this means the MQ client works as it did in version 7.5 and lower, MQCSP did not exist in those older versions and the username and password specified were sent in some fields called RemoteUserIdentifier and RemotePassword and were limited to 12 characters.

With MQ v8 and higher the IBM MQ classes for JMS now support a new way to send the username and password, this is the MQCSP struction. This has some benefits for example it can passwords that are longer than 12 characters. If WMQConstants.USER_AUTHENTICATION_MQCSP is TRUE the username and password specified will be sent in the MQCSP structure and the RemoteUserIdentifier that is used in compatibility mode is instead filled in with the username the process is running under and the RemotePassword field is left blank.

I can't find anything that leads me to believe that the userCredentialsConnectionFactoryAdapter bean does anything with the WMQConstants.USER_AUTHENTICATION_MQCSP setting so this would lead me to believe it should be sending the username and password you specified in the fields called RemoteUserIdentifier and RemotePassword. If you used the commented out code instead it would send the username and password you specified in the MQCSP and your windows login id that is running the process would be sent in the RemoteUserIdentifier field.


A queue manager at v8 with the configuration you have specified will completely disregard anything in the MQCSP structure and will only look at the RemoteUserIdentifier field. You also said CHLAUTH is DISABLED, so this means that there are no CHLAUTH rules in place that would restrict or change the username that the client sends. If the SVRCONN channel has a blank MCAUSER, then the userid sent in the RemoteUserIdentifier field will be used for queue authorization checks.

I suspect that when you noted your "windows login credential" coming across to the queue manager it was when you had WMQConstants.USER_AUTHENTICATION_MQCSP set to TRUE. By setting this to FALSE you can specify any ID you want to be sent to MQ and because of the configuration of the queue manager it will accept that ID and use it, the password is not verified.

Since the password is not checked by MQ it does not matter what value if any you specify for the password, the following would be able to connect sending the specified username.

mqQueueConnectionFactory.setBooleanProperty(WMQConstants.USER_AUTHENTICATION_MQCSP, FALSE);
mqQueueConnectionFactory.setStringProperty(WMQConstants.USERID, userName); 

Notes about the queue manager setup

Because CONNAUTH is not configured and CHLAUTH is also DISABLED you can connect to the queue manager and specify the mqm id (if the queue manager is on Unix) or the MUSR_MQADMIN id (if the queue manager is on Windows) and you would have full MQ authority over all queues on the queue manager.

The current configuration provides no security to prevent anyone that has access connect over the network to the queue manager's host and port from accessing any resources the queue manager has available, in most cases this could also be leveraged to execute anything you want on the server where the queue manager runs.

Upvotes: 2

Related Questions