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.