ulisesbocchio / spring-boot-security-saml

spring-security-saml integration with Spring Boot
MIT License
157 stars 72 forks source link

WebSecurityConfigurerAdapter - invalid bean definition #89

Open chriswhite199 opened 4 years ago

chriswhite199 commented 4 years ago

is there any advice for configuration ordering when trying to define you own WebSecurityConfigurerAdapter, as per the docs?

I get the following error when trying to create a WebSecurityConfigurerAdapter:


java.lang.IllegalStateException: Failed to load ApplicationContext

    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:125)
    at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:108)
    at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:190)
    at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:132)
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:246)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:227)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:246)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
    at org.junit.runners.Suite.runChild(Suite.java:128)
    at org.junit.runners.Suite.runChild(Suite.java:27)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Caused by: org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'saml' defined in com.github.ulisesbocchio.spring.boot.security.saml.configurer.WebSecurityConfigurerAdapterTest$MyWebSecurityConfigurerAdapter: Cannot register bean definition [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=webSecurityConfigurerAdapterTest.MyWebSecurityConfigurerAdapter; factoryMethodName=saml; initMethodName=null; destroyMethodName=(inferred); defined in com.github.ulisesbocchio.spring.boot.security.saml.configurer.WebSecurityConfigurerAdapterTest$MyWebSecurityConfigurerAdapter] for bean 'saml': There is already [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=com.github.ulisesbocchio.spring.boot.security.saml.configuration.SAMLServiceProviderSecurityConfiguration$SAMLWebSecurityConfigurer; factoryMethodName=saml; initMethodName=null; destroyMethodName=(inferred); defined in com.github.ulisesbocchio.spring.boot.security.saml.configuration.SAMLServiceProviderSecurityConfiguration$SAMLWebSecurityConfigurer] bound.
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.registerBeanDefinition(DefaultListableBeanFactory.java:897)
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod(ConfigurationClassBeanDefinitionReader.java:274)
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:141)
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:117)
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:327)
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:232)
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:275)
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:95)
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:705)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:531)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:316)
    at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:127)
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:117)
    ... 32 more

Here's a sample unit test that replicates

package com.github.ulisesbocchio.spring.boot.security.saml.configurer;

import com.github.ulisesbocchio.spring.boot.security.saml.annotation.EnableSAMLSSO;
import com.github.ulisesbocchio.spring.boot.security.saml.bean.SAMLConfigurerBean;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;

/**
 * @author Ulises Bocchio
 */

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@SpringBootTest(classes = WebSecurityConfigurerAdapterTest.MyWebSecurityConfigurerAdapter.class)
public class WebSecurityConfigurerAdapterTest {

    @SpringBootApplication
    @EnableSAMLSSO
    public static class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
        @Bean
        SAMLConfigurerBean saml() {
            return new SAMLConfigurerBean();
        }

        @Bean
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.httpBasic()
                    .disable()
                    .csrf()
                    .disable()
                    .anonymous()
                    .and()
                        .apply(saml())
                    .and()
                        .authorizeRequests()
                        .requestMatchers(saml().endpointsMatcher())
                        .permitAll()
                    .and()
                        .authorizeRequests()
                        .anyRequest()
                        .authenticated();
        }
    }

    @Test
    public void contextLoads() throws Exception {

    }

}

To work around this, if you move the @EnableSAMLSSO to another configuration class, that is lexicographically greater than the config for the adapter, it works:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@SpringBootTest(classes = {
        WebSecurityConfigurerAdapterTest.MyWebSecurityConfigurerAdapter.class,
        WebSecurityConfigurerAdapterTest.ZSSOConfig.class
})
public class WebSecurityConfigurerAdapterTest {

    @SpringBootApplication
    public static class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
        @Bean
        SAMLConfigurerBean saml() {
            return new SAMLConfigurerBean();
        }

        @Bean
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.httpBasic()
                    .disable()
                    .csrf()
                    .disable()
                    .anonymous()
                    .and()
                    .apply(saml())
                    .and()
                    .authorizeRequests()
                    .requestMatchers(saml().endpointsMatcher())
                    .permitAll()
                    .and()
                    .authorizeRequests()
                    .anyRequest()
                    .authenticated();
        }
    }

    @EnableSAMLSSO
    public static class ZSSOConfig {

    }

    @Test
    public void contextLoads() throws Exception {

    }

}

No amount of using [@]Order, implements Ordered or AutoConfigureXXX seems to resolve this without naming the second class accordingly.

I'm thinking this would be resolved by explicit AutoConfiguration of SAMLServiceProviderSecurityConfiguration in META-INF/spring.factories, as the ConditionalOnMissingBeans would be invoked after any other user defined [@]Configuration classes

chriswhite199 commented 4 years ago

One solution i've found is to use AutoConfiguration vs @EnableSAMLSSO.

Taking the first unit test from my previous post, if you comment out the @EnableSAMLSSO and instead add a META-INF/spring.factories file with the following contents, the test now passes:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.github.ulisesbocchio.spring.boot.security.saml.configuration.SAMLServiceProviderSecurityConfiguration

This does mean that you can't easily enable / disable without changing this file, but i'm assuming that a simple wrapper AutoConfiguration class (outside the scanning classpath) with a @ConditionalOnProperty should work.

So taking this to its logical conclusion:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.github.ulisesbocchio.spring.boot.security.saml.configurer.WebSecurityConfigurerAdapterTest.SamlAutoConfiguration
package com.github.ulisesbocchio.spring.boot.security.saml.configurer;

import com.github.ulisesbocchio.spring.boot.security.saml.annotation.EnableSAMLSSO;
import com.github.ulisesbocchio.spring.boot.security.saml.bean.SAMLConfigurerBean;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@SpringBootTest(classes = {
        WebSecurityConfigurerAdapterTest.MyWebSecurityConfigurerAdapter.class
},
properties = {
        "spring.security.saml.enabled=true"
})
public class WebSecurityConfigurerAdapterTest {

    @SpringBootApplication
    public static class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
        @Bean
        SAMLConfigurerBean saml() {
            return new SAMLConfigurerBean();
        }

        @Bean
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.httpBasic()
                    .disable()
                    .csrf()
                    .disable()
                    .anonymous()
                    .and()
                    .apply(saml())
                    .and()
                    .authorizeRequests()
                    .requestMatchers(saml().endpointsMatcher())
                    .permitAll()
                    .and()
                    .authorizeRequests()
                    .anyRequest()
                    .authenticated();
        }
    }

    @ConditionalOnProperty(value = "spring.security.saml.enabled", havingValue = "true")
    @EnableSAMLSSO
    public static class SamlAutoConfiguration {

    }

    @Test
    public void contextLoads() throws Exception {

    }

}
IcedCoffeeBoy commented 4 years ago

Hi i am also facing the same problem

org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'saml' defined in com.spring.MainApplication: Cannot register bean definition [Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=mainApplication; factoryMethodName=saml; initMethodName=null; destroyMethodName=(inferred); defined in com.spring.MainApplication] for bean 'saml': There is already [Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=com.github.ulisesbocchio.spring.boot.security.saml.configuration.SAMLServiceProviderSecurityConfiguration$SAMLWebSecurityConfigurer; factoryMethodName=saml; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [com/github/ulisesbocchio/spring/boot/security/saml/configuration/SAMLServiceProviderSecurityConfiguration$SAMLWebSecurityConfigurer.class]] bound.
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.registerBeanDefinition(DefaultListableBeanFactory.java:927) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod(ConfigurationClassBeanDefinitionReader.java:287) ~[spring-context-5.2.4.RELEASE

If i use ServiceProviderConfigurerAdapter method, the Configurer parameter does not seems to be set.

The project i working on have an existing WebSecurityConfigurerAdapter, is there anyway around this?