diff --git a/spring-boot-actuator/README.md b/spring-boot-actuator/README.md index 570fa99ded3..a9bc23502c1 100644 --- a/spring-boot-actuator/README.md +++ b/spring-boot-actuator/README.md @@ -292,16 +292,21 @@ Try it out: The default auto configuration has an in-memory user database with one entry, and the `` value has to be read from the logs (at INFO level) by default. If you want to extend or expand that, or -point to a database or directory server, you only need to provide a -`@Bean` definition for an `AuthenticationManager`, e.g. in your -`SampleController`: +point to a database or directory server, you can add the `@EnableGlobalAuthentication` +annotation and configure the global `AuthenticationManagerBuilder` as shown below: + @Controller + @EnableAutoConfiguration + @EnableGlobalAuthentication + public class SampleController { + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + auth.inMemoryAuthentication() + .withUser("client").password("secret").roles("USER"); + } - @Autowired - public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { - auth.inMemoryAuthentication() - .withUser("client").password("secret").roles("USER"); + ... } Try it out: diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementSecurityAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementSecurityAutoConfigurationTests.java index bfd02fc9528..74c67a1065e 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementSecurityAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementSecurityAutoConfigurationTests.java @@ -98,7 +98,9 @@ public class ManagementSecurityAutoConfigurationTests { private UserDetails getUser() { ProviderManager manager = this.context.getBean(ProviderManager.class); - DaoAuthenticationProvider provider = (DaoAuthenticationProvider) manager + ProviderManager parent = (ProviderManager) ReflectionTestUtils.getField( + manager, "parent"); + DaoAuthenticationProvider provider = (DaoAuthenticationProvider) parent .getProviders().get(0); UserDetailsService service = (UserDetailsService) ReflectionTestUtils.getField( provider, "userDetailsService"); diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/AuthenticationManagerConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/AuthenticationManagerConfiguration.java index bae272a5000..b09cdd82534 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/AuthenticationManagerConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/AuthenticationManagerConfiguration.java @@ -16,20 +16,26 @@ package org.springframework.boot.autoconfigure.security; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.security.SecurityProperties.User; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.config.annotation.ObjectPostProcessor; +import org.springframework.security.config.annotation.SecurityConfigurer; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.web.WebSecurityConfigurer; -import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter; /** * Configuration for a Spring Security in-memory {@link AuthenticationManager}. @@ -41,26 +47,77 @@ import org.springframework.security.config.annotation.web.builders.WebSecurity; @ConditionalOnMissingBean(AuthenticationManager.class) @ConditionalOnWebApplication @Order(Ordered.LOWEST_PRECEDENCE - 3) -public class AuthenticationManagerConfiguration implements - WebSecurityConfigurer { +public class AuthenticationManagerConfiguration extends GlobalAuthenticationConfigurerAdapter { - @Autowired - private SecurityProperties security; + private static Log logger = LogFactory.getLog(AuthenticationManagerConfiguration.class); @Autowired private List dependencies; - @Override - public void init(WebSecurity builder) throws Exception { - } - - @Override - public void configure(WebSecurity builder) throws Exception { - } + @Autowired + private ObjectPostProcessor objectPostProcessor; @Autowired - public void authentication(AuthenticationManagerBuilder builder) throws Exception { - SecurityAutoConfiguration.authentication(builder, this.security); + private SecurityProperties security; + + public void init(AuthenticationManagerBuilder auth) throws Exception { + auth.apply(new BootDefaultingAuthenticationConfigurerAdapter()); } -} + /** + * We must add {@link BootDefaultingAuthenticationConfigurerAdapter} in the + * init phase of the last {@link GlobalAuthenticationConfigurerAdapter}. The + * reason is that the typical flow is something like: + * + *
    + *
  • A + * {@link GlobalAuthenticationConfigurerAdapter#init(AuthenticationManagerBuilder)} + * exists that adds a {@link SecurityConfigurer} to the + * {@link AuthenticationManagerBuilder}
  • + *
  • + * {@link AuthenticationManagerConfiguration#init(AuthenticationManagerBuilder)} + * adds BootDefaultingAuthenticationConfigurerAdapter so it is after the + * {@link SecurityConfigurer} in the first step
  • + *
  • We then can default an {@link AuthenticationProvider} if necessary. + * Note we can only invoke the + * {@link AuthenticationManagerBuilder#authenticationProvider(AuthenticationProvider)} + * method since all other methods add a {@link SecurityConfigurer} which is + * not allowed in the configure stage. It is not allowed because we + * guarantee all init methods are invoked before configure, which cannot be + * guaranteed at this point.
  • + *
+ * + * @author Rob Winch + */ + private class BootDefaultingAuthenticationConfigurerAdapter extends GlobalAuthenticationConfigurerAdapter { + + @Override + public void configure(AuthenticationManagerBuilder auth) + throws Exception { + if(auth.isConfigured()) { + return; + } + + User user = AuthenticationManagerConfiguration.this.security.getUser(); + if (user.isDefaultPassword()) { + logger.info("\n\nUsing default password for application endpoints: " + + user.getPassword() + "\n\n"); + } + + AuthenticationManagerBuilder defaultAuth = new AuthenticationManagerBuilder(objectPostProcessor); + + Set roles = new LinkedHashSet(user.getRole()); + + AuthenticationManager parent = defaultAuth. + inMemoryAuthentication() + .withUser(user.getName()) + .password(user.getPassword()) + .roles(roles.toArray(new String[roles.size()])) + .and() + .and() + .build(); + + auth.parentAuthenticationManager(parent); + } + } +} \ No newline at end of file diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityAutoConfiguration.java index db9d3dabe0f..446f620ece2 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityAutoConfiguration.java @@ -16,28 +16,15 @@ package org.springframework.boot.autoconfigure.security; -import java.lang.reflect.Method; -import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.Set; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.security.SecurityProperties.User; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder; -import org.springframework.security.config.annotation.ObjectPostProcessor; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.util.ReflectionUtils; /** * {@link EnableAutoConfiguration Auto-configuration} for Spring Security. Provides an @@ -58,68 +45,9 @@ import org.springframework.util.ReflectionUtils; @Import({ SpringBootWebSecurityConfiguration.class, AuthenticationManagerConfiguration.class }) public class SecurityAutoConfiguration { - - private static Log logger = LogFactory.getLog(SecurityAutoConfiguration.class); - @Bean @ConditionalOnMissingBean public SecurityProperties securityProperties() { return new SecurityProperties(); } - - @Bean - @ConditionalOnBean(AuthenticationManagerBuilder.class) - @ConditionalOnMissingBean - public AuthenticationManager authenticationManager( - AuthenticationManagerBuilder builder, ObjectPostProcessor processor) - throws Exception { - if (!isBuilt(builder)) { - authentication(builder, securityProperties()); - } - else if (builder.getOrBuild() == null) { - builder = new AuthenticationManagerBuilder(processor); - authentication(builder, securityProperties()); - } - return builder.getOrBuild(); - } - - /** - * Convenience method for building the default AuthenticationManager from - * SecurityProperties. - * - * @param builder the AuthenticationManagerBuilder to use - * @param security the SecurityProperties in use - */ - public static void authentication(AuthenticationManagerBuilder builder, - SecurityProperties security) throws Exception { - - if (isBuilt(builder)) { - return; - } - - User user = security.getUser(); - - if (user.isDefaultPassword()) { - logger.info("\n\nUsing default password for application endpoints: " - + user.getPassword() + "\n\n"); - } - - Set roles = new LinkedHashSet(user.getRole()); - - builder.inMemoryAuthentication().withUser(user.getName()) - .password(user.getPassword()) - .roles(roles.toArray(new String[roles.size()])); - - } - - private static boolean isBuilt(AuthenticationManagerBuilder builder) { - Method configurers = ReflectionUtils.findMethod( - AbstractConfiguredSecurityBuilder.class, "getConfigurers"); - Method unbuilt = ReflectionUtils.findMethod( - AbstractConfiguredSecurityBuilder.class, "isUnbuilt"); - ReflectionUtils.makeAccessible(configurers); - ReflectionUtils.makeAccessible(unbuilt); - return !((Collection) ReflectionUtils.invokeMethod(configurers, builder)) - .isEmpty() || !((Boolean) ReflectionUtils.invokeMethod(unbuilt, builder)); - } -} +} \ No newline at end of file