/* * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.boot.autoconfigure.security; import java.lang.reflect.Field; 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.NoSuchBeanDefinitionException; import org.springframework.beans.factory.SmartInitializingSingleton; 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.security.SecurityProperties.User; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationEventPublisher; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.ProviderManager; 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.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter; import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer; import org.springframework.util.ReflectionUtils; /** * Configuration for a Spring Security in-memory {@link AuthenticationManager}. Can be * disabled by providing a bean of type AuthenticationManager, or by autowiring an * {@link AuthenticationManagerBuilder} into a method in one of your configuration * classes. The value provided by this configuration will become the "global" * authentication manager (from Spring Security), or the parent of the global instance. * Thus it acts as a fallback when no others are provided, is used by method security if * enabled, and as a parent authentication manager for "local" authentication managers in * individual filter chains. * * @author Dave Syer * @author Rob Winch */ @Configuration @ConditionalOnBean(ObjectPostProcessor.class) @ConditionalOnMissingBean({ AuthenticationManager.class }) @Order(0) public class AuthenticationManagerConfiguration { private static final Log logger = LogFactory .getLog(AuthenticationManagerConfiguration.class); @Bean @Primary public AuthenticationManager authenticationManager( AuthenticationConfiguration configuration) throws Exception { return configuration.getAuthenticationManager(); } @Bean public static SpringBootAuthenticationConfigurerAdapter springBootAuthenticationConfigurerAdapter( SecurityProperties securityProperties, List<SecurityPrerequisite> dependencies) { return new SpringBootAuthenticationConfigurerAdapter(securityProperties); } @Bean public AuthenticationManagerConfigurationListener authenticationManagerConfigurationListener() { return new AuthenticationManagerConfigurationListener(); } /** * {@link GlobalAuthenticationConfigurerAdapter} to apply * {@link DefaultInMemoryUserDetailsManagerConfigurer}. We must apply * {@link DefaultInMemoryUserDetailsManagerConfigurer} in the init phase of the last * {@link GlobalAuthenticationConfigurerAdapter}. The reason is that the typical flow * is something like: * * <ul> * <li>A * {@link GlobalAuthenticationConfigurerAdapter#init(AuthenticationManagerBuilder)} * exists that adds a {@link SecurityConfigurer} to the * {@link AuthenticationManagerBuilder}.</li> * <li>{@link AuthenticationManagerConfiguration#init(AuthenticationManagerBuilder)} * adds {@link SpringBootAuthenticationConfigurerAdapter} so it is after the * {@link SecurityConfigurer} in the first step.</li> * <li>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. * </li> * </ul> */ @Order(Ordered.LOWEST_PRECEDENCE - 100) private static class SpringBootAuthenticationConfigurerAdapter extends GlobalAuthenticationConfigurerAdapter { private final SecurityProperties securityProperties; SpringBootAuthenticationConfigurerAdapter(SecurityProperties securityProperties) { this.securityProperties = securityProperties; } @Override public void init(AuthenticationManagerBuilder auth) throws Exception { auth.apply(new DefaultInMemoryUserDetailsManagerConfigurer( this.securityProperties)); } } /** * {@link InMemoryUserDetailsManagerConfigurer} to add user details from * {@link SecurityProperties}. This is necessary to delay adding the default user. * * <ul> * <li>A {@link GlobalAuthenticationConfigurerAdapter} will initialize the * {@link AuthenticationManagerBuilder} with a Configurer which will be after any * {@link GlobalAuthenticationConfigurerAdapter}.</li> * <li>{@link SpringBootAuthenticationConfigurerAdapter} will be invoked after all * {@link GlobalAuthenticationConfigurerAdapter}, but before the Configurers that were * added by other {@link GlobalAuthenticationConfigurerAdapter} instances.</li> * <li>A {@link SpringBootAuthenticationConfigurerAdapter} will add * {@link DefaultInMemoryUserDetailsManagerConfigurer} after all Configurer instances. * </li> * <li>All init methods will be invoked.</li> * <li>All configure methods will be invoked which is where the * {@link AuthenticationProvider} instances are setup.</li> * <li>If no AuthenticationProviders were provided, * {@link DefaultInMemoryUserDetailsManagerConfigurer} will default the value.</li> * </ul> */ private static class DefaultInMemoryUserDetailsManagerConfigurer extends InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> { private final SecurityProperties securityProperties; DefaultInMemoryUserDetailsManagerConfigurer( SecurityProperties securityProperties) { this.securityProperties = securityProperties; } @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { if (auth.isConfigured()) { return; } User user = this.securityProperties.getUser(); if (user.isDefaultPassword()) { logger.info(String.format("%n%nUsing default security password: %s%n", user.getPassword())); } Set<String> roles = new LinkedHashSet<>(user.getRole()); withUser(user.getName()).password(user.getPassword()) .roles(roles.toArray(new String[roles.size()])); setField(auth, "defaultUserDetailsService", getUserDetailsService()); super.configure(auth); } private void setField(Object target, String name, Object value) { try { Field field = ReflectionUtils.findField(target.getClass(), name); ReflectionUtils.makeAccessible(field); ReflectionUtils.setField(field, target, value); } catch (Exception ex) { logger.info("Could not set " + name); } } } /** * {@link ApplicationListener} to autowire the {@link AuthenticationEventPublisher} * into the {@link AuthenticationManager}. */ protected static class AuthenticationManagerConfigurationListener implements SmartInitializingSingleton { @Autowired private AuthenticationEventPublisher eventPublisher; @Autowired private ApplicationContext context; @Override public void afterSingletonsInstantiated() { try { configureAuthenticationManager( this.context.getBean(AuthenticationManager.class)); } catch (NoSuchBeanDefinitionException ex) { // Ignore } } private void configureAuthenticationManager(AuthenticationManager manager) { if (manager instanceof ProviderManager) { ((ProviderManager) manager) .setAuthenticationEventPublisher(this.eventPublisher); } } } }