javaspringjmsibm-mqmq

How to resolve IBM MQ call failed with compcode '2' ('MQCC_FAILED') reason '2035' ('MQRC_NOT_AUTHORIZED')


I have a Spring project where I need to connect to IBM MQ to send and receive messages from but I'm getting the below exception:-

IBM MQ call failed with compcode '2' ('MQCC_FAILED') reason '2035' ('MQRC_NOT_AUTHORIZED'). JMSWMQ2013: The security authentication was not valid that was supplied for queue manager '<queuemanager name' with connection mode 'Client' and host name '()'.

My Config class

@Bean
public JmsTemplate jmsTemplate() throws JMSException {
    MQQueueConnectionFactory mqQueueConnectionFactory = mqQueueConnectionFactory();
    UserCredentialsConnectionFactoryAdapter userCredentialsConnectionFactoryAdapter =
            getUserCredentialsConnectionFactoryAdapter(mqQueueConnectionFactory);
    JmsTemplate jmsTemplate = new JmsTemplate(userCredentialsConnectionFactoryAdapter);
    jmsTemplate.setReceiveTimeout(Long.parseLong(receiveTimeout));
    return jmsTemplate;
}

@Bean
public MQQueueConnectionFactory mqQueueConnectionFactory() throws JMSException {
    MQQueueConnectionFactory mqQueueConnectionFactory = new MQQueueConnectionFactory();
    mqQueueConnectionFactory.setHostName(hostname);
    mqQueueConnectionFactory.setTransportType(WMQConstants.WMQ_CM_CLIENT);
    mqQueueConnectionFactory.setUserAuthenticationMQCSP(Boolean.parseBoolean(userMQCSPAuthValue));
    mqQueueConnectionFactory.setChannel(channel);
    mqQueueConnectionFactory.setPort(port);
    mqQueueConnectionFactory.setQueueManager(queueManager);
    mqQueueConnectionFactory.setClientReconnectOptions(WMQConstants.WMQ_CLIENT_RECONNECT);
    mqQueueConnectionFactory.setClientReconnectTimeout(Integer.parseInt(reconnectTimeout));
    return mqQueueConnectionFactory;
}

@Bean
public UserCredentialsConnectionFactoryAdapter getUserCredentialsConnectionFactoryAdapter(
        MQQueueConnectionFactory mqQueueConnectionFactory) {
    UserCredentialsConnectionFactoryAdapter userCredentialsConnectionFactoryAdapter =
            new UserCredentialsConnectionFactoryAdapter();
    userCredentialsConnectionFactoryAdapter.setUsername(username);
    userCredentialsConnectionFactoryAdapter.setPassword(password);
    userCredentialsConnectionFactoryAdapter.setTargetConnectionFactory(mqQueueConnectionFactory);
    return userCredentialsConnectionFactoryAdapter;
}

Usage

TextMessage message = (TextMessage) jmsTemplate.sendAndReceive(destinationMq, session -> {
                TextMessage textMessage = session.createTextMessage(request);
                textMessage.setJMSCorrelationID(correlationId);
                textMessage.setJMSReplyTo(getMQ(replyToQueue));
                return textMessage;
            });

Server Logs

AMQ9557E: Queue Manager User ID initialization failed for 'my system user id'. EXPLANATION: The call to initialize the User ID 'my system user id' failed with CompCode 2 and Reason 2035. If an MQCSP block was used, the User ID in the MQCSP block was 'username from property'. If a userID flow was used, the User ID in the UID header was 'my system user id' and any CHLAUTH rules applied prior to user adoption were evaluated case-sensitively against this value. ACTION: Correct the error and try again.

Facts
Username and password are correct other applications use the same credentials.

Solution Tried and Worked
I was able to resolve the issue by using the below two methods:-

  1. Setting the setUserAuthenticationMQCSP value to false

  2. Setting System.property("user.name", "user name from property") at startup

Questions
The above two solutions seem hacky and don't actually solve the actual issue.

  1. Why the above two solutions worked?

  2. Why my system user id is being used to authenticate the connection to IBM MQ? How to ensure that connection is authenticated only using the passed credentials?


Solution

  • IBM MQ (aka MQSeries, WebSphere MQ) has been around for 30 years, so part of your problem is that MQ is trying to support backwards compatibility, so that old applications don't break when a new version of MQ is released. At least that is the thinking at IBM.

    Old school code (prior to MQ v6) was that the UserId and Password were taken from the RemoteUserIdentifier and RemotePassword fields of the MQCD structure for authentication.

    The MQCSP structure was introduced in MQ v6.0, if I remember correctly. Since then, this is the preferred way of sending UserId and Password to the queue manager for authentication by either a channel security exit or the queue manager itself (queue manager authentication was introduced in MQ v8).

    The MQ Java client library prior to MQ v7.1 (I think) sent a blank RemoteUserIdentifier which wasn't good because of an underlining security hole or feature, depending on your point of view. Since, then the MQ Java client library has made an attempt to determine what is the UserId that the process (aka JVM) is running under and put it in the both the CSPUserId field of the MQCSP structure when the JVM environment variable setUserAuthenticationMQCSP is to true. When setUserAuthenticationMQCSP is to false then the MQ client library is the RemoteUserIdentifier field with the processes' UserId. Or possibly is is set for both no matter what and the setUserAuthenticationMQCSP setting is set in the CapabilityFlags field of the MQCXP structure.

    I'm sure that is clear as mud! Time for an example.

    Let's say your JVM/process is running under UserId 'fred'. Your application wants to authenticate the connection to the queue manager using UserId 'barney' and Password 'mypwd00'.

    When you originally ran your application (before your changes/updates), the queue manager would be using the UserId 'barney' if the queue manager is configured for authentication and the UserId 'fred' for authorizations. i.e. can you connect to the queue manager, can you open the queue, etc... Since, UserId 'fred' does not have permission, your application receives Reason Code of 2035 (not authorized).

    As JoshMc pointed out, if you set ADOPTCTX(YES) then the queue manager would adopt/switch to using the authenticated UserId.

    The reason setting setUserAuthenticationMQCSP to false did not fix your issue, is because it is only telling the MQ Java client library where (which structure) to put the UserId and Password that you are specifying on your JMS call and not what to use.

    By setting System.property("user.name", "barney"), the MQ Java client library puts the specified UserId in the RemoteUserIdendtifier field to be the same UserId as the authenticating UserId, i.e. 'barney' in both cases, the queue manager will use it to perform authorizations, hence, your application works.