Describe the bug
org.springframework.security.config.crypto.RsaKeyConversionServicePostProcessor does not affect the ConversionService that is used by org.springframework.boot.context.properties.bind.Binder and IMO here's why:
First, somehow ApplicationConversionService.getSharedInstance() gets created and gets processed by the post processor above. Then org.springframework.boot.context.properties.ConversionServiceDeducer#getConversionService gets called in org.springframework.boot.context.properties.ConfigurationPropertiesBinder#getBinder and it has this code:
ConversionService getConversionService() {
try {
return this.applicationContext.getBean(ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME,
ConversionService.class);
}
catch (NoSuchBeanDefinitionException ex) {
return new Factory(this.applicationContext.getAutowireCapableBeanFactory()).create();
}
}
Here, the bean is not found for some reason, so it gets created. However, the conversion service already exists and is accessible by ApplicationConversionService.getSharedInstance(). But this time the post-processor is not invoked.
To Reproduce
@ConfigurationProperties("jwt")
@Data
public class JwtProperties {
private RSAPublicKey key; // doesn't work
}
@Autowired
public void bla(@Value("${jwt.key}") RSAPublicKey key){} // works
jwt.key: classpath:rsa_public.pem
Bash to generate the file:
# generate private key
openssl genpkey -algorithm RSA -out rsa_private_pkcs1.pem -pkeyopt rsa_keygen_bits:2048
# extract public key from the private key. Base64 content is supported by Java's X509EncodedKeySpec.
openssl rsa -in rsa_private_pkcs1.pem -pubout -out rsa_public.pem
# translate private key from the default PKCS1 format
# into PKCS8 that is supported by Java's PKCS8EncodedKeySpec (also in Base64).
openssl pkcs8 -topk8 -in rsa_private_pkcs1.pem -out rsa_private.pem -nocrypt
# delete the PKCS1 private key version
rm rsa_private_pkcs1.pem
Comment From: jzheaux
Thanks for the report, @Sam-Kruglov.
I attempted to reproduce your issue by altering the oauth2ResourceServer-static sample, but was unsuccessful. If I create a new class called JwtProperties as you described, Spring populates the key field as expected.
Would you be able to put together a minimal GitHub sample that reproduces the issue?
Comment From: Sam-Kruglov
Sure, will provide
Comment From: Sam-Kruglov
Done: https://github.com/Sam-Kruglov/issue9316
Turns out there's some kind of conflict coming from flyway, take a look at the application.yml.
Comment From: jzheaux
Thanks, @Sam-Kruglov, for the extra info.
Spring Boot does not yet have @ConfigurationPropertiesBinding support for these two converters. I'll leave this ticket open while I'm taking a look at that.
In the meantime, it will work if you specify your own @ConfigurationPropertiesBinding classes:
private static final ResourceLoader resourceLoader = new DefaultResourceLoader();
@Component
@ConfigurationPropertiesBinding
class PrivateKeyConverter implements Converter<String, RSAPrivateKey> {
@Override
public RSAPrivateKey convert(String location) {
try (InputStream is = resourceLoader.getResource(location).getInputStream()) {
return RsaKeyConverters.pkcs8().convert(is);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
@Component
@ConfigurationPropertiesBinding
class PublicKeyConverter implements Converter<String, RSAPublicKey> {
@Override
public RSAPublicKey convert(String location) {
try (InputStream is = resourceLoader.getResource(location).getInputStream()) {
return RsaKeyConverters.x509().convert(is);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
Comment From: jzheaux
I've added ResourceKeyConverterAdapter in https://github.com/spring-projects/spring-security/commit/65d3b0d71cc26d7f57809060448a37e926eb16e9 to reduce some of the boilerplate indicated above. Now, you can instead do:
@Bean
@ConfigurationPropertiesBinding
Converter<String, RSAPrivateKey> privateKeys() {
return new ResourceKeyConverterAdapter<>(RsaKeyConverters.pkcs8());
}
@Bean
@ConfigurationPropertiesBinding
Converter<String, RSAPublicKey> publicKeys() {
return new ResourceKeyConverterAdapter<>(RsaKeyConverters.x509());
}
I've also filed https://github.com/spring-projects/spring-boot/issues/24891 to investigate adding the above to Boot's Security auto-configuration.
I'm going to close this as addressed since I don't anticipate any further Security commits to address the issue. We can reopen if more work is needed.
Comment From: jzheaux
@Sam-Kruglov, it looks like the Boot team addressed this in https://github.com/spring-projects/spring-boot/issues/26089. When I update the sample project in this ticket to use Spring Boot 2.5.0-RC1, it starts up fine without publishing its own @ConfigurationPropertiesBinding.
I've added a ticket to inline ResourceKeyConverterAdapter, since it appears to not be necessary at this time.