001/*
002 * Copyright 2012-2016 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.autoconfigure.security;
018
019import java.lang.reflect.Field;
020import java.util.LinkedHashSet;
021import java.util.List;
022import java.util.Set;
023
024import org.apache.commons.logging.Log;
025import org.apache.commons.logging.LogFactory;
026
027import org.springframework.beans.factory.NoSuchBeanDefinitionException;
028import org.springframework.beans.factory.SmartInitializingSingleton;
029import org.springframework.beans.factory.annotation.Autowired;
030import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
031import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
032import org.springframework.boot.autoconfigure.security.SecurityProperties.User;
033import org.springframework.context.ApplicationContext;
034import org.springframework.context.ApplicationListener;
035import org.springframework.context.annotation.Bean;
036import org.springframework.context.annotation.Configuration;
037import org.springframework.context.annotation.Primary;
038import org.springframework.core.Ordered;
039import org.springframework.core.annotation.Order;
040import org.springframework.security.authentication.AuthenticationEventPublisher;
041import org.springframework.security.authentication.AuthenticationManager;
042import org.springframework.security.authentication.AuthenticationProvider;
043import org.springframework.security.authentication.ProviderManager;
044import org.springframework.security.config.annotation.ObjectPostProcessor;
045import org.springframework.security.config.annotation.SecurityConfigurer;
046import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
047import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
048import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter;
049import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
050import org.springframework.util.ReflectionUtils;
051
052/**
053 * Configuration for a Spring Security in-memory {@link AuthenticationManager}. Can be
054 * disabled by providing a bean of type AuthenticationManager, or by autowiring an
055 * {@link AuthenticationManagerBuilder} into a method in one of your configuration
056 * classes. The value provided by this configuration will become the "global"
057 * authentication manager (from Spring Security), or the parent of the global instance.
058 * Thus it acts as a fallback when no others are provided, is used by method security if
059 * enabled, and as a parent authentication manager for "local" authentication managers in
060 * individual filter chains.
061 *
062 * @author Dave Syer
063 * @author Rob Winch
064 */
065@Configuration
066@ConditionalOnBean(ObjectPostProcessor.class)
067@ConditionalOnMissingBean({ AuthenticationManager.class })
068@Order(0)
069public class AuthenticationManagerConfiguration {
070
071        private static final Log logger = LogFactory
072                        .getLog(AuthenticationManagerConfiguration.class);
073
074        @Bean
075        @Primary
076        public AuthenticationManager authenticationManager(
077                        AuthenticationConfiguration configuration) throws Exception {
078                return configuration.getAuthenticationManager();
079        }
080
081        @Bean
082        public static SpringBootAuthenticationConfigurerAdapter springBootAuthenticationConfigurerAdapter(
083                        SecurityProperties securityProperties,
084                        List<SecurityPrerequisite> dependencies) {
085                return new SpringBootAuthenticationConfigurerAdapter(securityProperties);
086        }
087
088        @Bean
089        public AuthenticationManagerConfigurationListener authenticationManagerConfigurationListener() {
090                return new AuthenticationManagerConfigurationListener();
091        }
092
093        /**
094         * {@link GlobalAuthenticationConfigurerAdapter} to apply
095         * {@link DefaultInMemoryUserDetailsManagerConfigurer}. We must apply
096         * {@link DefaultInMemoryUserDetailsManagerConfigurer} in the init phase of the last
097         * {@link GlobalAuthenticationConfigurerAdapter}. The reason is that the typical flow
098         * is something like:
099         *
100         * <ul>
101         * <li>A
102         * {@link GlobalAuthenticationConfigurerAdapter#init(AuthenticationManagerBuilder)}
103         * exists that adds a {@link SecurityConfigurer} to the
104         * {@link AuthenticationManagerBuilder}.</li>
105         * <li>{@link AuthenticationManagerConfiguration#init(AuthenticationManagerBuilder)}
106         * adds {@link SpringBootAuthenticationConfigurerAdapter} so it is after the
107         * {@link SecurityConfigurer} in the first step.</li>
108         * <li>We then can default an {@link AuthenticationProvider} if necessary. Note we can
109         * only invoke the
110         * {@link AuthenticationManagerBuilder#authenticationProvider(AuthenticationProvider)}
111         * method since all other methods add a {@link SecurityConfigurer} which is not
112         * allowed in the configure stage. It is not allowed because we guarantee all init
113         * methods are invoked before configure, which cannot be guaranteed at this point.
114         * </li>
115         * </ul>
116         */
117        @Order(Ordered.LOWEST_PRECEDENCE - 100)
118        private static class SpringBootAuthenticationConfigurerAdapter
119                        extends GlobalAuthenticationConfigurerAdapter {
120
121                private final SecurityProperties securityProperties;
122
123                SpringBootAuthenticationConfigurerAdapter(SecurityProperties securityProperties) {
124                        this.securityProperties = securityProperties;
125                }
126
127                @Override
128                public void init(AuthenticationManagerBuilder auth) throws Exception {
129                        auth.apply(new DefaultInMemoryUserDetailsManagerConfigurer(
130                                        this.securityProperties));
131                }
132
133        }
134
135        /**
136         * {@link InMemoryUserDetailsManagerConfigurer} to add user details from
137         * {@link SecurityProperties}. This is necessary to delay adding the default user.
138         *
139         * <ul>
140         * <li>A {@link GlobalAuthenticationConfigurerAdapter} will initialize the
141         * {@link AuthenticationManagerBuilder} with a Configurer which will be after any
142         * {@link GlobalAuthenticationConfigurerAdapter}.</li>
143         * <li>{@link SpringBootAuthenticationConfigurerAdapter} will be invoked after all
144         * {@link GlobalAuthenticationConfigurerAdapter}, but before the Configurers that were
145         * added by other {@link GlobalAuthenticationConfigurerAdapter} instances.</li>
146         * <li>A {@link SpringBootAuthenticationConfigurerAdapter} will add
147         * {@link DefaultInMemoryUserDetailsManagerConfigurer} after all Configurer instances.
148         * </li>
149         * <li>All init methods will be invoked.</li>
150         * <li>All configure methods will be invoked which is where the
151         * {@link AuthenticationProvider} instances are setup.</li>
152         * <li>If no AuthenticationProviders were provided,
153         * {@link DefaultInMemoryUserDetailsManagerConfigurer} will default the value.</li>
154         * </ul>
155         */
156        private static class DefaultInMemoryUserDetailsManagerConfigurer
157                        extends InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> {
158
159                private final SecurityProperties securityProperties;
160
161                DefaultInMemoryUserDetailsManagerConfigurer(
162                                SecurityProperties securityProperties) {
163                        this.securityProperties = securityProperties;
164                }
165
166                @Override
167                public void configure(AuthenticationManagerBuilder auth) throws Exception {
168                        if (auth.isConfigured()) {
169                                return;
170                        }
171                        User user = this.securityProperties.getUser();
172                        if (user.isDefaultPassword()) {
173                                logger.info(String.format("%n%nUsing default security password: %s%n",
174                                                user.getPassword()));
175                        }
176                        Set<String> roles = new LinkedHashSet<String>(user.getRole());
177                        withUser(user.getName()).password(user.getPassword())
178                                        .roles(roles.toArray(new String[roles.size()]));
179                        setField(auth, "defaultUserDetailsService", getUserDetailsService());
180                        super.configure(auth);
181                }
182
183                private void setField(Object target, String name, Object value) {
184                        try {
185                                Field field = ReflectionUtils.findField(target.getClass(), name);
186                                ReflectionUtils.makeAccessible(field);
187                                ReflectionUtils.setField(field, target, value);
188                        }
189                        catch (Exception ex) {
190                                logger.info("Could not set " + name);
191                        }
192                }
193
194        }
195
196        /**
197         * {@link ApplicationListener} to autowire the {@link AuthenticationEventPublisher}
198         * into the {@link AuthenticationManager}.
199         */
200        protected static class AuthenticationManagerConfigurationListener
201                        implements SmartInitializingSingleton {
202
203                @Autowired
204                private AuthenticationEventPublisher eventPublisher;
205
206                @Autowired
207                private ApplicationContext context;
208
209                @Override
210                public void afterSingletonsInstantiated() {
211                        try {
212                                configureAuthenticationManager(
213                                                this.context.getBean(AuthenticationManager.class));
214                        }
215                        catch (NoSuchBeanDefinitionException ex) {
216                                // Ignore
217                        }
218                }
219
220                private void configureAuthenticationManager(AuthenticationManager manager) {
221                        if (manager instanceof ProviderManager) {
222                                ((ProviderManager) manager)
223                                                .setAuthenticationEventPublisher(this.eventPublisher);
224                        }
225                }
226
227        }
228
229}