Spring-boot: How can I exclude the configuration enabled through a Boot protected inner configuration class?

Created on 16 Mar 2016  路  11Comments  路  Source: spring-projects/spring-boot

I have a concrete situation where I want to exclude the configuration implemented by
org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerTokenServicesConfiguration$JwtTokenServicesConfiguration

I tried both exclude = {ResourceServerTokenServicesConfiguration.class} and excludeName = {"org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerTokenServicesConfiguration$JwtTokenServicesConfiguration"} but none is working.

The scenario is that I want to implement my own ResourceServerTokenServices but when the upstream beans have to autowire this bean, I get this error:

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [org.springframework.security.oauth2.provider.token.TokenStore] is defined: expected single matching bean but found 2: customJwtTokenStore,jwtTokenStore
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1126) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1014) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:545) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    ... 19 common frames omitted

Clearly, the boot JwtTokenServicesConfiguration configuration is not omitted. There would be a hack here where I can make the JwtTokenCondition fail, but this is a low hack.

Here is the simple configuration that I'm using:

@SpringBootApplication
@EnableAutoConfiguration(exclude = {ResourceServerTokenServicesConfiguration.class},
        excludeName = {"org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerTokenServicesConfiguration$JwtTokenServicesConfiguration"})
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true)
public class Elephant
{
    public static void main(String[] args) {
        SpringApplication.run(Elephant.class, args);
    }

    @Configuration
    @EnableResourceServer
    protected static class SecurityConfiguration extends ResourceServerConfigurerAdapter
    {

        @Autowired
        @Qualifier("customJwtTokenEnhancer")
        JwtAccessTokenConverter customJwtTokenEnhancer;

        @Autowired
        @Qualifier("customJwtTokenStore")
        TokenStore customJwtTokenStore;

        @Autowired
        @Qualifier("customJwtTokenServices")
        ResourceServerTokenServices customJwtTokenServices;

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception
        {
            resources.tokenServices(customJwtTokenServices);
        }

        @Bean
        public ResourceServerTokenServices customJwtTokenServices() {
            DefaultTokenServices services = new DefaultTokenServices();
            services.setTokenStore(customJwtTokenStore);
            return services;
        }

        @Bean
        public TokenStore customJwtTokenStore() {
            return new JwtTokenStore(customJwtTokenEnhancer);
        }

        @Bean
        @Autowired
        public JwtAccessTokenConverter customJwtTokenEnhancer(
                @Value("${security.oauth2.resource.jwt.keyValue}") String keyValue) {
            JwtAccessTokenConverter converter = new JwtAccessTokenConverter(){
                @Override
                public OAuth2Authentication extractAuthentication(Map<String, ?> map)
                {
                    OAuth2Authentication authentication = super.extractAuthentication(map);
                    Map<String, String> details = new HashMap<>();
                    details.put("account_id", (String) map.get("account_id"));
                    authentication.setDetails(details);
                    return authentication;
                }
            };
            if (keyValue != null) {
                converter.setVerifierKey(keyValue);
            }
            return converter;
        }
    }
}

this is the configuration file:

server.port = 8081

logging.level.org.springframework.security=DEBUG
security.sessions=stateless
security.oauth2.resource.jwt.keyValue=-----BEGIN PUBLIC KEY-----[[MY KEY]]-----END PUBLIC KEY-----

and the dependencies:

dependencyManagement {
    imports {
        mavenBom "org.springframework.boot:spring-boot-starter-parent:1.3.3.RELEASE"
    }
}

dependencies {
    compile('org.springframework.boot:spring-boot-starter-jdbc')
    compile('org.springframework.boot:spring-boot-starter-security')
    compile('org.springframework.boot:spring-boot-starter-web')
    compile('net.sf.ehcache:ehcache:2.10.1')

    compile("org.springframework.security:spring-security-acl:4.0.3.RELEASE")
    compile('org.springframework.security.oauth:spring-security-oauth2:2.0.9.RELEASE')
    compile('org.springframework.security:spring-security-jwt')

    compile('org.json:json:20150729')
    compile('org.apache.commons:commons-lang3:3.1')
    runtime('mysql:mysql-connector-java')

    testCompile('org.springframework.boot:spring-boot-starter-test')
    testCompile('org.testng:testng:6.9.8')
    testCompile('org.hamcrest:hamcrest-all:1.3')
}
declined

Most helpful comment

@snicoll and @dsyer: I was having the similar problem with configuration exclusion. A simple unit test for PasswordEncoder doesn't require to load the EmbeddedMongoDb in the spring context. @EnableAutoConfiguration exclude the inner configurations and not load embeddedMongoDb, but then it throw the error that these are not auto configurations. Is it default forced behaviour?

My unit test works, but it loads the embeddedMongoDb in the spring context which scores a penalty on performance.

EnableAutoConfiguration Error

java.lang.IllegalStateException: The following classes could not be excluded because they are not auto-configuration classes:
    - com.<enclosedForTheSakeOfConfidentiality>.<handler>.config.TestMongoConfig

Spring Context

java.lang.IllegalStateException: Failed to load ApplicationContext

Suggestion

There should be a new annotation @ExcludeInnerConfiguration or @DisableInnerConfiguration - which will similarly take the placeholder classes and exclude them from the spring context.

@ExcludeInnerConfiguration(classes = {
        MongoConfig.class,
        TestMongoConfig.class
})

OR

@DisableInnerConfiguration(exclude = {
        MongoConfig.class,
        TestMongoConfig.class
})

The sole responsibility of exclusion would be the developer and he/she knows the intentions behind why? and @philwebb don't need to check case-by-case why it was discarded or even required/needed.

All 11 comments

You can't. Exclude only works on auto-configuration classes (top level classes) referenced in the documentation.

As for the concrete problem, I am sure @dsyer knows more than I am.

I guess I don't see a problem with making JwtTokenCondition fail (that's what it's there for). An alternative would be to not create a @Bean for your TokenStore, but I think that would just lead to issues later.

This is what I actually did in the end.

But that is "all or nothing" in terms of what Spring Boot can do for you in the future. Maybe there is a need for finer control on what Spring boot auto-config capabilities can be turned on and off. And then, this breaks the standardization of the configuration. Again, this could be problematic in the future.

The side story is that I needed this to address this issue (714).

I'm not the expert in this case, so I'm just posting my concerns.
Thanks for the advice though.

+1 for a way of disabling autoconfiguration in inner classes. I wanted to do the same (disable inner class in Jackson Joda Time autoconfiguration) and ended up workarounding it by not customizing the configuration.

We can't easily disable inner-configurations. I'd rather take each need on a case-by-case basis and work out why a specific inner-configuration couldn't be used. @dfernandezm Feel free to raise a new issue if you think there is some additional @Condition guard that we need for Jackson + Joda Time.

We're not keen to increase the complexity of excludes. The inner configs are really meant as an implementation detail.

Is there a way to specify package name for exclude?

Would be easy if there is an option to specify "org.apache.camel" in below case.
e.g: packckageExclude={"org.apache.camel"}

@SpringBootApplication(exclude = {
        org.apache.camel.spring.boot.security.CamelSSLAutoConfiguration.class
        , org.apache.camel.component.bean.springboot.BeanComponentAutoConfiguration.class
        ,org.apache.camel.component.beanclass.springboot.ClassComponentAutoConfiguration.class
        , org.apache.camel.component.binding.springboot.BindingNameComponentAutoConfiguration.class
        ,org.apache.camel.component.browse.springboot.BrowseComponentAutoConfiguration.class
        ,org.apache.camel.component.controlbus.springboot.ControlBusComponentAutoConfiguration.class
        , org.apache.camel.component.dataformat.springboot.DataFormatComponentAutoConfiguration.class
        ,org.apache.camel.component.dataset.springboot.DataSetComponentAutoConfiguration.class
        ,org.apache.camel.component.direct.springboot.DirectComponentAutoConfiguration.class
        ,org.apache.camel.component.directvm.springboot.DirectVmComponentAutoConfiguration.class
        ,org.apache.camel.spring.boot.health.HealthCheckRoutesAutoConfiguration.class
        ,org.apache.camel.component.file.springboot.FileComponentAutoConfiguration.class
        , org.apache.camel.component.jackson.springboot.JacksonDataFormatAutoConfiguration.class
        ,org.apache.camel.component.jpa.springboot.JpaComponentAutoConfiguration.class
        ,org.apache.camel.component.language.springboot.LanguageComponentAutoConfiguration.class
,org.apache.camel.component.log.springboot.LogComponentAutoConfiguration.class

The camel packages are using RelaxedPropertyResolver which is no more present in springboot 2.0

Caused by: java.lang.ClassNotFoundException: org.springframework.boot.bind.RelaxedPropertyResolver
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:338)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)

@espre05 This issue is closed and we prefer not to use the tracker for questions. Please ask things like this on stackoverflow.com or join us on gitter.

This is still an issue with Spring Boot Autoconfiguration.

There should be a way to disable a built-in autoconfiguration and provide more logic in there.

Real use case: we use a secret store and need to use its APIs to get the RSA public key (JWT token verifier key). We need to change the implementation (that can only read the token value from an URL) and we have no way to change the default implementation.

This should be considered an enhancement request, not a question.

There should be a way to disable a built-in autoconfiguration and provide more logic in there.

Nobody disagree with that and @philwebb already answered that concern in a comment above:

I'd rather take each need on a case-by-case basis and work out why a specific inner-configuration couldn't be used.

Please share your use case in a separate issue. A small sample that illustrates what you are doing right now is preferable to help us locate the problem.

This should be considered an enhancement request, not a question.

This wasn't a question, this was a request that got declined, see this comment above and the status: declined label on the right.

@snicoll and @dsyer: I was having the similar problem with configuration exclusion. A simple unit test for PasswordEncoder doesn't require to load the EmbeddedMongoDb in the spring context. @EnableAutoConfiguration exclude the inner configurations and not load embeddedMongoDb, but then it throw the error that these are not auto configurations. Is it default forced behaviour?

My unit test works, but it loads the embeddedMongoDb in the spring context which scores a penalty on performance.

EnableAutoConfiguration Error

java.lang.IllegalStateException: The following classes could not be excluded because they are not auto-configuration classes:
    - com.<enclosedForTheSakeOfConfidentiality>.<handler>.config.TestMongoConfig

Spring Context

java.lang.IllegalStateException: Failed to load ApplicationContext

Suggestion

There should be a new annotation @ExcludeInnerConfiguration or @DisableInnerConfiguration - which will similarly take the placeholder classes and exclude them from the spring context.

@ExcludeInnerConfiguration(classes = {
        MongoConfig.class,
        TestMongoConfig.class
})

OR

@DisableInnerConfiguration(exclude = {
        MongoConfig.class,
        TestMongoConfig.class
})

The sole responsibility of exclusion would be the developer and he/she knows the intentions behind why? and @philwebb don't need to check case-by-case why it was discarded or even required/needed.

Was this page helpful?
0 / 5 - 0 ratings