我有一个 Spring 项目,需要连接到 IBM MQ 来发送和接收消息,但出现以下异常:-
IBM MQ 调用失败,代码为“2”(“MQCC_FAILED”),原因为“2035” ('MQRC_NOT_AUTHORIZED')。 JMSWMQ2013:安全认证是 为队列管理器提供的无效'
我的配置类
@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;
}
用法
TextMessage message = (TextMessage) jmsTemplate.sendAndReceive(destinationMq, session -> {
TextMessage textMessage = session.createTextMessage(request);
textMessage.setJMSCorrelationID(correlationId);
textMessage.setJMSReplyTo(getMQ(replyToQueue));
return textMessage;
});
服务器日志
AMQ9557E:“我的系统”的队列管理器用户 ID 初始化失败 用户身份'。说明:调用初始化用户 ID“我的系统” 用户 ID' 失败,CompCode 2 和原因 2035。如果 MQCSP 块 使用时,MQCSP 块中的用户 ID 是“来自属性的用户名”。如果一个用户ID 使用流程时,UID 标头中的用户 ID 为“我的系统用户 id” 并评估用户采用之前应用的任何 CHLAUTH 规则 对此值区分大小写。行动:纠正错误并尝试 再次。
事实
用户名和密码正确,其他应用程序使用相同的凭据。
已尝试并有效的解决方案
我使用以下两种方法解决了这个问题:-
将 setUserAuthenticationMQCSP 值设置为 false
启动时设置 System.property("user.name", "user name from property")
问题
以上两种解决方案看起来很hacky,并没有真正解决实际问题。
为什么上述两种解决方案有效?
为什么我的系统用户 ID 用于验证与 IBM MQ 的连接?如何确保仅使用传递的凭据对连接进行身份验证?
IBM MQ(又名 MQSeries、WebSphere MQ)已经存在了 30 年,所以您的部分问题是 MQ 试图支持向后兼容性,以便在发布新版本的 MQ 时旧应用程序不会中断。至少 IBM 是这么想的。
旧式代码(MQ v6 之前)是从 MQCD 结构的 RemoteUserIdentifier 和 RemotePassword 字段中获取 UserId 和 Password 来进行身份验证。
如果我没记错的话,MQCSP 结构是在 MQ v6.0 中引入的。从那时起,这是将 UserId 和密码发送到队列管理器以通过通道安全出口或队列管理器本身进行身份验证的首选方式(队列管理器身份验证是在 MQ v8 中引入的)。
MQ v7.1 之前的 MQ Java 客户端库(我认为)发送了一个空白的 RemoteUserIdentifier,这并不好,因为存在潜在的安全漏洞或功能,具体取决于您的观点。此后,当 JVM 环境变量 setUserAuthenticationMQCSP 为 true 时,MQ Java 客户端库尝试确定进程(又名 JVM)正在运行的 UserId 并将其放入 MQCSP 结构的 CSPUserId 字段中。当 setUserAuthenticationMQCSP 为 false 时,MQ 客户端库是带有进程 UserId 的 RemoteUserIdentifier 字段。或者可能是为两者设置了无论如何,并且 setUserAuthenticationMQCSP 设置在 MQCXP 结构的 CapabilityFlags 字段中设置。
我确信那是干净如泥的!是时候举个例子了。
假设您的 JVM/进程在 UserId“fred”下运行。您的应用程序想要使用用户 ID“barney”和密码“mypwd00”来验证与队列管理器的连接。
当您最初运行应用程序时(在更改/更新之前),如果队列管理器配置为进行身份验证,则队列管理器将使用 UserId 'barney' 并使用 UserId 'fred' 进行授权。即,您可以连接到队列管理器吗?您可以打开队列吗?等等...由于 UserId 'fred' 没有权限,您的应用程序会收到原因代码 2035(未授权)。
正如 JoshMc 指出的,如果您设置
ADOPTCTX(YES)
,那么队列管理器将采用/切换到使用经过身份验证的 UserId。
将 setUserAuthenticationMQCSP 设置为 false 没有解决您的问题的原因是,它只是告诉 MQ Java 客户端库将您在 JMS 调用上指定的 UserId 和密码放在哪里(哪个结构),而不是使用什么。
通过设置
System.property("user.name", "barney")
,MQ Java 客户端库将 RemoteUserIdendtifier 字段中指定的 UserId 与身份验证 UserId 相同,即两种情况下的“barney”,队列管理器将使用它来执行授权,因此,您的应用程序有效。