mq-jms-spring
mq-jms-spring copied to clipboard
Spring Boot 3.1 SSL Bundles Support Not Working
mq-jms-spring-boot-starter:3.1.2 Java17
spring:
ssl:
bundle:
jks:
mq-ssl-bundle:
key:
alias: "mq-ssl-bundle"
keystore:
type: PKCS12
location: key.p12
password: password
ibm:
mq:
conn-name: localhost(1424)
queue-manager: QQQ1
channel: CLNT.E
user: user_00
ssl-bundle: "mq-ssl-bundle"
When you use Spring boot 3.1 SSL Bundles, it's unable to get SSL bundle properly
MQConfigurationSslBundles.getSSLSocketFactory
is getting called before bundles get initialized in the constructor
@Component
public class MQConfigurationSslBundles {
private static Logger logger = LoggerFactory.getLogger(MQConfigurationSslBundles.class);
static SslBundles bundles = null;
/* This is called during the initialisation phase */
public MQConfigurationSslBundles(SslBundles sslBundles) {
logger.trace("constructor - Bundles are {}", (sslBundles == null) ? "null" : "not null");
bundles = sslBundles;
}
static boolean isSupported() {
logger.trace("SSLBundles are supported");
return true;
}
/* If the bundle name does not exist, then getBundle throws an exception. Since
there is always some default bundle, we can't rely on there being no bundle.
So we log an error, but otherwise try to continue.
*/
public static SSLSocketFactory getSSLSocketFactory(String b) {
SSLSocketFactory sf = null;
logger.trace("getSSLSocketFactory for {}", b);
if (b == null || b.isEmpty()) {
return sf;
}
if (bundles != null) {
try {
SslBundle sb = bundles.getBundle(b);
sf = sb.createSslContext().getSocketFactory();
}
catch (NoSuchSslBundleException e) {
logger.error("No SSL bundle found for {}", b);
}
}
return sf;
}
}
What evidence do you have for this? Have you run a program with trace-level logging to show the sequence?
The sample application (s2.tls.jms3) works fine for me. And the configuration dependencies tell Spring to drive the SslBundle stuff before any of the other configuration.
I have run into the same issue whe using the starter. Spring initializes the MQConfigurationSslBundles after the ConnectionFactory has been created. An easy solution would be to just remove the call to the static method, and make sure that you inject all your dependencies? My experience is that static methods never works well in a Spring application.
I managed to get things working by creating a MQConnectionFactoryCustomizer that sets the SslSocketFactory before initialization is completed. That could perhaps be a nice setup in the starter also?
@Bean
public MQConnectionFactoryCustomizer sslCustomizer(
Optional<SslBundles> bundles, Optional<MQConfigurationProperties> properties) {
log.debug("Creating MQConnectionFactoryCustomizer for SslBundles");
if (bundles.isEmpty() || properties.isEmpty()) {
return mqConnectionFactory -> {};
}
return connectionFactory -> {
if (properties.get().getSslBundle() != null) {
log.debug("Trying to configure MqConnectionFactory to use bundle {}", properties.get().getSslBundle());
SslBundle bundle = bundles.get().getBundle(properties.get().getSslBundle());
if (bundle != null) {
SSLSocketFactory socketFactory = bundle.createSslContext().getSocketFactory();
log.debug("Found bundle, created SocketFactory {}", socketFactory);
connectionFactory.setSSLSocketFactory(socketFactory);
}
}
};
}
I was hoping that one could use @ConditionalOnBeans-annotation, but that didn't work as expected.
I think I now have the sequencing right so the SSLBundles get initialised before other properties. My tests are showing it called at a suitable time.
(Some of the implementation is done the way it is in order to simplify having a mostly-common set of code with the JMS2/Spring2 variant where the SSLBundles classes are not available.)
My plan is to release the update soon to pick up the next version of the MQ client.
Unfortunately, it is still not working. Tested it with spring boot 3.1.5 and mq-jms-spring-boot-starter version 3.1.5. The problem is that MQConfigurationSslBundles.getSSLSocketFactory
is called by MQConnectionFactoryFactory.createConnectionFactory
before a new instance of MQConfigurationSslBundles
is created. Therefore the static variable MQConfigurationSslBundles.sslbundles
, which is initialized in the constructor, is null and the call to MQConfigurationSslBundles.getSSLSocketFactory
fails.
However, as a workaround I added a small configuration class (hack ;-)) to our project which fixes this issue so that the ssl connections can be established:
/**
* The constructor {@code MQConfigurationSslBundles} needs to be called before {@code MQConfigurationSslBundles.getSSLSocketFactory}
* because otherwise the ssl connection can't be established. The mq-jms-spring has an issue that the class {@code MQConfigurationSslBundles}
* is instantiated only after the call to {@code MQConfigurationSslBundles.getSSLSocketFactory} which leads to an exception.
* This bugfix guarantes that the {@code MQConfigurationSslBundles} constructor is directly called after creating the ssl bundles
* so that the static instance variables in {@code MQConfigurationSslBundles} are initialized before the {@code MQConfigurationSslBundles.getSSLSocketFactory} call.
*
* @see <a href="https://github.com/ibm-messaging/mq-jms-spring/issues/96">mq-jms-spring issue regarding this fix</a>
* @see org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration which creates an instance of DefaultSslBundleRegistry by default.
*/
@Configuration
public class MqConfigurationFix {
@Bean
public DefaultSslBundleRegistry sslBundleRegistry(List<SslBundleRegistrar> sslBundleRegistrars) {
// copied from org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration
DefaultSslBundleRegistry registry = new DefaultSslBundleRegistry();
sslBundleRegistrars.forEach((registrar) -> {
registrar.registerBundles(registry);
});
// that's the bugfix code which initializes the static instance variables in MQConfigurationSslBundles
new MQConfigurationSslBundles(registry);
return registry;
}
}
For further analysis why MQConnectionFactoryFactory.createConnectionFactory
is called so early I added a breakpoint to this method and exported the stacktrace:
createConnectionFactory:69, MQConnectionFactoryFactory (com.ibm.mq.spring.boot)
createConnectionFactory:80, MQConnectionFactoryConfiguration (com.ibm.mq.spring.boot)
cachingJmsConnectionFactory:66, MQConnectionFactoryConfiguration$RegularMQConnectionFactoryConfiguration (com.ibm.mq.spring.boot)
invoke0:-1, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:77, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect)
invoke:568, Method (java.lang.reflect)
instantiate:139, SimpleInstantiationStrategy (org.springframework.beans.factory.support)
instantiate:650, ConstructorResolver (org.springframework.beans.factory.support)
instantiateUsingFactoryMethod:642, ConstructorResolver (org.springframework.beans.factory.support)
instantiateUsingFactoryMethod:1332, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBeanInstance:1162, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:560, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:520, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:325, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, AbstractBeanFactory$$Lambda$323/0x0000000800e19118 (org.springframework.beans.factory.support)
getSingleton:234, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:323, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:199, AbstractBeanFactory (org.springframework.beans.factory.support)
resolveCandidate:254, DependencyDescriptor (org.springframework.beans.factory.config)
doResolveDependency:1417, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveDependency:1337, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveAutowiredArgument:910, ConstructorResolver (org.springframework.beans.factory.support)
createArgumentArray:788, ConstructorResolver (org.springframework.beans.factory.support)
instantiateUsingFactoryMethod:545, ConstructorResolver (org.springframework.beans.factory.support)
instantiateUsingFactoryMethod:1332, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBeanInstance:1162, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:560, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:520, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:325, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, AbstractBeanFactory$$Lambda$323/0x0000000800e19118 (org.springframework.beans.factory.support)
getSingleton:234, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:323, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:199, AbstractBeanFactory (org.springframework.beans.factory.support)
resolveCandidate:254, DependencyDescriptor (org.springframework.beans.factory.config)
doResolveDependency:1417, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveDependency:1337, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveAutowiredArgument:910, ConstructorResolver (org.springframework.beans.factory.support)
createArgumentArray:788, ConstructorResolver (org.springframework.beans.factory.support)
autowireConstructor:240, ConstructorResolver (org.springframework.beans.factory.support)
autowireConstructor:1352, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBeanInstance:1189, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:560, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:520, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:325, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, AbstractBeanFactory$$Lambda$323/0x0000000800e19118 (org.springframework.beans.factory.support)
getSingleton:234, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:323, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:199, AbstractBeanFactory (org.springframework.beans.factory.support)
resolveCandidate:254, DependencyDescriptor (org.springframework.beans.factory.config)
doResolveDependency:1417, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveDependency:1337, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveAutowiredArgument:910, ConstructorResolver (org.springframework.beans.factory.support)
createArgumentArray:788, ConstructorResolver (org.springframework.beans.factory.support)
autowireConstructor:240, ConstructorResolver (org.springframework.beans.factory.support)
autowireConstructor:1352, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBeanInstance:1189, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:560, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:520, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:325, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, AbstractBeanFactory$$Lambda$323/0x0000000800e19118 (org.springframework.beans.factory.support)
getSingleton:234, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:323, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:199, AbstractBeanFactory (org.springframework.beans.factory.support)
resolveCandidate:254, DependencyDescriptor (org.springframework.beans.factory.config)
doResolveDependency:1417, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveDependency:1337, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveAutowiredArgument:910, ConstructorResolver (org.springframework.beans.factory.support)
createArgumentArray:788, ConstructorResolver (org.springframework.beans.factory.support)
autowireConstructor:240, ConstructorResolver (org.springframework.beans.factory.support)
autowireConstructor:1352, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBeanInstance:1189, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:560, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:520, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:325, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, AbstractBeanFactory$$Lambda$323/0x0000000800e19118 (org.springframework.beans.factory.support)
getSingleton:234, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:323, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:199, AbstractBeanFactory (org.springframework.beans.factory.support)
resolveCandidate:254, DependencyDescriptor (org.springframework.beans.factory.config)
doResolveDependency:1417, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveDependency:1337, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveAutowiredArgument:910, ConstructorResolver (org.springframework.beans.factory.support)
createArgumentArray:788, ConstructorResolver (org.springframework.beans.factory.support)
autowireConstructor:240, ConstructorResolver (org.springframework.beans.factory.support)
autowireConstructor:1352, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBeanInstance:1189, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:560, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:520, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:325, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, AbstractBeanFactory$$Lambda$323/0x0000000800e19118 (org.springframework.beans.factory.support)
getSingleton:234, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:323, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:199, AbstractBeanFactory (org.springframework.beans.factory.support)
resolveCandidate:254, DependencyDescriptor (org.springframework.beans.factory.config)
doResolveDependency:1417, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveDependency:1337, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveAutowiredArgument:910, ConstructorResolver (org.springframework.beans.factory.support)
createArgumentArray:788, ConstructorResolver (org.springframework.beans.factory.support)
autowireConstructor:240, ConstructorResolver (org.springframework.beans.factory.support)
autowireConstructor:1352, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBeanInstance:1189, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:560, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:520, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:325, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, AbstractBeanFactory$$Lambda$323/0x0000000800e19118 (org.springframework.beans.factory.support)
getSingleton:234, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:323, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:199, AbstractBeanFactory (org.springframework.beans.factory.support)
preInstantiateSingletons:973, DefaultListableBeanFactory (org.springframework.beans.factory.support)
finishBeanFactoryInitialization:950, AbstractApplicationContext (org.springframework.context.support)
refresh:616, AbstractApplicationContext (org.springframework.context.support)
refresh:146, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:738, SpringApplication (org.springframework.boot)
refreshContext:440, SpringApplication (org.springframework.boot)
run:316, SpringApplication (org.springframework.boot)
run:1306, SpringApplication (org.springframework.boot)
run:1295, SpringApplication (org.springframework.boot)
main:38, Startup (***********)
Running into this issue, took me a while to figure out what was going on cause nothing useful gets logged. Can confirm that the workaround by @mschwartau works
I can't get this to work either for the same reason (v3.1.5), so I'm reverting back to using the old (now deprecated) properties ibm.mq.jks.-store. I hope this issue gets resolved before the old properties are removed.
I've finally got some time available to take another look at this, but it would be very helpful if someone provided a complete testcase to demonstrate the issue. My own tests work fine.
@ibmmqmet Have you considered using
@DependsOn("ibm.mq-com.ibm.mq.spring.boot.MQConfigurationProperties")
instead of
@AutoConfigureBefore({MQConfigurationProperties.class})
in MqConfigurationSslBundles.class?
I suspect that @AutoConfigureBefore-annotation only works for classes annotated with @AutoConfiguration and not @Configuration.
To reproduce, I think you need a different reason to set up the ssl context early on. We have an as400 sql datasource with secure=true that probably kicks in too early.
Simply adding the following bean also does trick
@Bean public MQConnectionFactoryCustomizer noOpCustomizer(MQConfigurationSslBundles ignored) { return cf -> {}; }
Might be a little mystical why it works at first glance, but it basically forces the creation of MQConfigurationSslBundles
before the ConnectionFactory
is created in MQConnectionFactoryConfiguration
using the static method com.ibm.mq.spring.boot.MQConnectionFactoryFactory#createConnectionFactory
.
The key here is that the ConnectionFactory
bean depends on MQConnectionFactoryCustomizer
, which ultimately leads to the final ordering.
Thanks for the suggestions on possible solutions. I've just released a new level that (hopefully) will deal with it - certainly I can see the ordering is slightly different.
But I've not been able to create a working broken test scenarion, so not been able to definitively prove the changes work.
The ssl bundles support was reworked in the latest version now that there's no need to have common code with boot 2. Ought to be much cleaner now.