spring-projects / spring-boot

Spring Boot
https://spring.io/projects/spring-boot
Apache License 2.0
74.51k stars 40.54k forks source link

Not possible to disable security with @ConditionalOnProperty anymore #12323

Closed peterdnight closed 6 years ago

peterdnight commented 6 years ago

Overall - I really like/prefer the new 2.0 security integration. The only issue is how can security be enabled/disabled via configuration.

In 1.5.x - security could be enabled / disabled via property. Unfortunately @ConditionalOnProperty seems to keep class available , so the new SpringBootWebSecurityConfiguration, @ConditionalOnClass(WebSecurityConfigurerAdapter.class) seems to find it any way, and configures default settings (http basic, everything protected, etc).

While debugging - I removed the @Configuration from MySecurityConfiguration , and security was not applied. So if there is some conditional way to @Import the file - that might also work.

Suggestions appreciated, Peter

Works in 1.5.x:

@Configuration
@ConditionalOnProperty ( "my.security.enabled" )
public class MySecurityConfiguration extends WebSecurityConfigurerAdapter {
     <working config>
}

Workaround for 2.x: Add an additional configuration with ! property, and ignore all

@Configuration
@ConditionalOnProperty (  name = "my.security.enabled", havingValue = "false" )
public class MySecurityDisabledConfiguration extends WebSecurityConfigurerAdapter {

    public void configure ( WebSecurity web ) throws Exception {
        web.ignoring().anyRequest() ;

    }
}

Spring default handler:

@ConditionalOnClass(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
public class SpringBootWebSecurityConfiguration {

    @Configuration
    @Order(SecurityProperties.BASIC_AUTH_ORDER)
    static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {

    }

}
wilkinsona commented 6 years ago

I'm not sure that I understand how things were working for you with 1.5.x. In 1.5, the security auto-configuration will apply irrespective of whether or not you've declared your own WebSecurityConfigurerAdapter. If you set my.security.enabled to true, you'll get Boot's security and your own security. If you set my.security.enabled to false you'll just get Boot's security.

I don't consider what you've done for 2.x to be a workaround. It's one of the approaches that I would recommend. Another that you could consider would be to disable the registration of Spring Security's servlet filter. What's most appropriate really depends on what you're trying to achieve by disabling security.

peterdnight commented 6 years ago

Scenario: support multiple security settings exclusively through boot configuration: (disabled, in memory, ldap, active directory,...) During desktop development - some times - it is ideal to have as slim a context loaded as possible - hence the disabled option.

In 1.5, I combined my.security.enabled along with spring boot properties: security.basic.enabled , etc. to prevent any security beans from being loaded.

If a single additional ConditionalOnProperty element is added to SpringBootWebSecurityConfiguration.java - this would enable 0 Spring Security beans to be configured. The reason the workaround is less then ideal is that a lot of beans are getting added into the context.

somthing like: (do this unless?) @ConditionalOnProperty ( name = "spring.security.disabled", havingValue = "null" )

wilkinsona commented 6 years ago

Adding a property was considered at length in https://github.com/spring-projects/spring-boot/issues/10306. I don't think anything's happened since then to change my opinion. Have you considered disabling the auto-configuration using the spring.autoconfigure.exclude property?

peterdnight commented 6 years ago

I found this issue after adding the exclude. While the bean does not get constructed, the WebSecurityConfigurerAdapter is detected as being present and the boot security initialization proceeds.

I actually debugged this by having a single class unit test (shown below) - using a very slim context @SpringBootApplication - and found that none of the SpringSecurity classes are loaded with the exclude added.

Having any bean - even if not instantiated - but extending WebSecurityConfigurerAdapter - causes SpringBoot initialization of security beans, regardless of the exclude:

@Configuration
@ConditionalOnProperty ( "my.security.enabled" )
public class MySecurityConfiguration extends WebSecurityConfigurerAdapter {
     <working config>
}

Test class to validate working exclude - not no beans extending WebSecurityConfigurerAdapter are present:

@RunWith ( SpringRunner.class )
@SpringBootTest ( classes = A_Simple_Application.Simple_Application.class , webEnvironment = WebEnvironment.RANDOM_PORT )
@ActiveProfiles ( "junit" )
@DirtiesContext
public class A_Simple_Application {
    final static private Logger logger = LoggerFactory.getLogger( A_Simple_Application.class );

    @BeforeClass
    // @Before
    static public void setUpBeforeClass ()
            throws Exception {

        System.out.println( "Starting logging" );
        CsapTest.initialize( logger.getName() );
    }

    @Autowired
    private ApplicationContext applicationContext;

    /**
     * 
     * Simple test app that excludes security autoconfiguration
     * ManagementWebSecurityAutoConfiguration.class
     */
    @SpringBootApplication ( exclude = {
            SecurityAutoConfiguration.class
    } )
    public static class Simple_Application {

        @RestController
        static public class Hello {

            @GetMapping ( "/hi" )
            public String hi () {
                return "Hello" +
                        LocalDateTime.now()
                            .format( DateTimeFormatter
                                .ofPattern( "HH:mm:ss,   MMMM d  uuuu " ) );
            }

            @Inject
            ObjectMapper jsonMapper;

        }

    }

    @Test
    public void load_context () {
        logger.info( CsapTest.TC_HEAD );

        logger.info( "beans loaded: {}", applicationContext.getBeanDefinitionCount() );

        assertThat( applicationContext.getBeanDefinitionCount() )
            .as( "Spring Bean count" )
            .isGreaterThan( 200 );

        // Assert.assertFalse( true);

    }

    @LocalServerPort
    private int testPort;

    @Inject
    RestTemplateBuilder restTemplateBuilder;

    @Test
    public void http_get_hi_from_simple_app ()
            throws Exception {
        String simpleUrl = "http://localhost:" + testPort + "/hi";

        logger.info( CsapTest.TC_HEAD + "hitting url: {}", simpleUrl );
        // mock does much validation.....

        TestRestTemplate restTemplate = new TestRestTemplate( restTemplateBuilder );

        ResponseEntity<String> response = restTemplate.getForEntity( simpleUrl, String.class );

        logger.info( "result:\n" + response );

        assertThat( response.getBody() )
            .startsWith( "Hello" );
    }

}
wilkinsona commented 6 years ago

Sorry, I don't understand your latest comment.

and found that none of the SpringSecurity classes are loaded with the exclude added.

That's what I would expect. With Boot's auto-configuration excluded Security is not configured unless you do so yourself. That's what you want to happen, isn't it?

While the bean does not get constructed, the WebSecurityConfigurerAdapter is detected as being present and the initialization proceeds.

I don't understand what you're referring to here. The initialisation of what proceeds?

Rather than pasting code (that doesn't compile) into an issue comment, perhaps you could provide an attachment or repository on GitHub that precisely illustrates the problem without requiring any modification?

peterdnight commented 6 years ago

Thanks for suggestions and links. After playing with the exclude option - desired behavior is occurring. I switched to the following convention, to enable security to be completely configuration driven using a single property. I was concerned that there would be potential for ordering issues - but my unit tests are all passing.

Then in my security configuration:


@Configuration
@ConditionalOnProperty (  "my.security.enabled" )
@Import ( SecurityAutoConfiguration.class 
public class MySecurityConfiguration extends WebSecurityConfigurerAdapter {

}
wilkinsona commented 6 years ago

Excellent. Thanks for following up.

thephw commented 5 years ago

FWIW, worked a similar solution using profiles for configuring secure and insecure profiles.

The secure profile loads OAUTH2 config from environment variables for real operation. The insecure profile skips auth for integration tests with an external service.

Put it all in a demo repo here: https://github.com/deftinc/spring_azure_ad_profile/