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}