/* * Copyright 2002-2013 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.security.config.annotation.web.configuration; import javax.servlet.Filter; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.DefaultAuthenticationEventPublisher; 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.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer; import org.springframework.security.config.annotation.web.configurers.SecurityContextConfigurer; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; /** * Provides a convenient base class for creating a {@link WebSecurityConfigurer} * instance. The implementation allows customization by overriding methods. * * @see EnableWebSecurity * * @author Rob Winch */ public abstract class WebSecurityConfigurerAdapter implements SecurityConfigurer<Filter,WebSecurity> { private final Log logger = LogFactory.getLog(WebSecurityConfigurerAdapter.class); private ApplicationContext context; private ObjectPostProcessor<Object> objectPostProcessor = new ObjectPostProcessor<Object>() { @Override public <T> T postProcess(T object) { throw new IllegalStateException(ObjectPostProcessor.class.getName()+ " is a required bean. Ensure you have used @EnableWebSecurity and @Configuration"); } }; private final AuthenticationManagerBuilder authenticationBuilder = new AuthenticationManagerBuilder(); private final AuthenticationManagerBuilder parentAuthenticationBuilder = new AuthenticationManagerBuilder() { @Override public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) { authenticationBuilder.eraseCredentials(eraseCredentials); return super.eraseCredentials(eraseCredentials); } }; private boolean disableAuthenticationRegistration; private boolean authenticationManagerInitialized; private AuthenticationManager authenticationManager; private HttpSecurity http; private boolean disableDefaults; /** * Creates an instance with the default configuration enabled. */ protected WebSecurityConfigurerAdapter() { this(false); } /** * Creates an instance which allows specifying if the default configuration * should be enabled. Disabling the default configuration should be * considered more advanced usage as it requires more understanding of how * the framework is implemented. * * @param disableDefaults * true if the default configuration should be enabled, else * false */ protected WebSecurityConfigurerAdapter(boolean disableDefaults) { this.disableDefaults = disableDefaults; } /** * Used by the default implementation of {@link #authenticationManager()} to attempt to obtain an * {@link AuthenticationManager}. If overridden, the {@link AuthenticationManagerBuilder} should be used to specify * the {@link AuthenticationManager}. The resulting {@link AuthenticationManager} * will be exposed as a Bean as will the last populated {@link UserDetailsService} that is created with the * {@link AuthenticationManagerBuilder}. The {@link UserDetailsService} will also automatically be populated on * {@link HttpSecurity#getSharedObject(Class)} for use with other {@link SecurityContextConfigurer} * (i.e. RememberMeConfigurer ) * * <p>For example, the following configuration could be used to register * in memory authentication that exposes an in memory {@link UserDetailsService}:</p> * * <pre> * @Override * protected void registerAuthentication(AuthenticationManagerBuilder auth) { * registry * // enable in memory based authentication with a user named "user" and "admin" * .inMemoryAuthentication() * .withUser("user").password("password").roles("USER").and() * .withUser("admin").password("password").roles("USER", "ADMIN"); * } * </pre> * * @param auth the {@link AuthenticationManagerBuilder} to use * @throws Exception */ protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception { this.disableAuthenticationRegistration = true; } /** * Creates the {@link HttpSecurity} or returns the current instance * * @return the {@link HttpSecurity} * @throws Exception */ protected final HttpSecurity getHttp() throws Exception { if(http != null) { return http; } authenticationBuilder.objectPostProcessor(objectPostProcessor); parentAuthenticationBuilder.objectPostProcessor(objectPostProcessor); DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher()); parentAuthenticationBuilder.authenticationEventPublisher(eventPublisher); AuthenticationManager authenticationManager = authenticationManager(); authenticationBuilder.parentAuthenticationManager(authenticationManager); http = new HttpSecurity(objectPostProcessor,authenticationBuilder, parentAuthenticationBuilder.getSharedObjects()); http.setSharedObject(UserDetailsService.class, userDetailsService()); if(!disableDefaults) { http .exceptionHandling().and() .sessionManagement().and() .securityContext().and() .requestCache().and() .anonymous().and() .servletApi().and() .apply(new DefaultLoginPageConfigurer<HttpSecurity>()).and() .logout(); } configure(http); return http; } /** * Override this method to expose the {@link AuthenticationManager} from * {@link #registerAuthentication(AuthenticationManagerBuilder)} to be exposed as * a Bean. For example: * * <pre> * @Bean(name name="myAuthenticationManager") * @Override * public AuthenticationManager authenticationManagerBean() throws Exception { * return super.authenticationManagerBean(); * } * </pre> * * @return the {@link AuthenticationManager} * @throws Exception */ public AuthenticationManager authenticationManagerBean() throws Exception { return new AuthenticationManagerDelegator(authenticationBuilder); } /** * Gets the {@link AuthenticationManager} to use. The default strategy is if * {@link #registerAuthentication(AuthenticationManagerBuilder)} method is * overridden to use the {@link AuthenticationManagerBuilder} that was passed in. * Otherwise, autowire the {@link AuthenticationManager} by type. * * @return * @throws Exception */ protected AuthenticationManager authenticationManager() throws Exception { if(!authenticationManagerInitialized) { registerAuthentication(parentAuthenticationBuilder); if(disableAuthenticationRegistration) { try { authenticationManager = context.getBean(AuthenticationManager.class); } catch(NoSuchBeanDefinitionException e) { logger.debug("The AuthenticationManager was not found. This is ok for now as it may not be required.",e); } } else { authenticationManagerInitialized = true; authenticationManager = parentAuthenticationBuilder.build(); } authenticationManagerInitialized = true; } return authenticationManager; } /** * Override this method to expose a {@link UserDetailsService} created from * {@link #registerAuthentication(AuthenticationManagerBuilder)} as a bean. In * general only the following override should be done of this method: * * <pre> * @Bean(name = "myUserDetailsService") // any or no name specified is allowed * @Override * public UserDetailsService userDetailsServiceBean() throws Exception { * return super.userDetailsServiceBean(); * } * </pre> * * To change the instance returned, developers should change * {@link #userDetailsService()} instead * @return * @throws Exception * @see {@link #userDetailsService()} */ public UserDetailsService userDetailsServiceBean() throws Exception { return userDetailsService(); } /** * Allows modifying and accessing the {@link UserDetailsService} from * {@link #userDetailsServiceBean()()} without interacting with the * {@link ApplicationContext}. Developers should override this method when * changing the instance of {@link #userDetailsServiceBean()}. * * @return */ protected UserDetailsService userDetailsService() { return parentAuthenticationBuilder.getDefaultUserDetailsService(); } @Override public void init(final WebSecurity web) throws Exception { final HttpSecurity http = getHttp(); web .addSecurityFilterChainBuilder(http) .postBuildAction(new Runnable() { @Override public void run() { FilterSecurityInterceptor securityInterceptor = http.getSharedObject(FilterSecurityInterceptor.class); web.securityInterceptor(securityInterceptor); } }); } /** * Override this method to configure {@link WebSecurity}. For * example, if you wish to ignore certain requests. */ @Override public void configure(WebSecurity web) throws Exception { } /** * Override this method to configure the {@link HttpSecurity}. * Typically subclasses should not invoke this method by calling super * as it may override their configuration. The default configuration is: * * <pre> * http * .authorizeUrls() * .anyRequest().authenticated().and() * .formLogin().and() * .httpBasic(); * </pre> * * @param http * the {@link HttpSecurity} to modify * @throws Exception * if an error occurs */ protected void configure(HttpSecurity http) throws Exception { logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity)."); http .authorizeUrls() .anyRequest().authenticated() .and() .formLogin().and() .httpBasic(); } @Autowired public void setApplicationContext(ApplicationContext context) { this.context = context; } @Autowired(required=false) public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) { this.objectPostProcessor = objectPostProcessor; } /** * Delays the use of the {@link AuthenticationManager} build from the * {@link AuthenticationManagerBuilder} to ensure that it has been fully * configured. * * @author Rob Winch * @since 3.2 */ static final class AuthenticationManagerDelegator implements AuthenticationManager { private AuthenticationManagerBuilder delegateBuilder; private AuthenticationManager delegate; private final Object delegateMonitor = new Object(); AuthenticationManagerDelegator(AuthenticationManagerBuilder authentication) { this.delegateBuilder = authentication; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { if(delegate != null) { return delegate.authenticate(authentication); } synchronized(delegateMonitor) { if (delegate == null) { delegate = this.delegateBuilder.getObject(); this.delegateBuilder = null; } } return delegate.authenticate(authentication); } } }