|
@@ -1,38 +1,33 @@
|
|
|
package org.springframework.security.config;
|
|
|
|
|
|
-import java.util.ArrayList;
|
|
|
-import java.util.Collections;
|
|
|
import java.util.Iterator;
|
|
|
-import java.util.List;
|
|
|
import java.util.Map;
|
|
|
|
|
|
-import javax.servlet.Filter;
|
|
|
-
|
|
|
import org.apache.commons.logging.Log;
|
|
|
import org.apache.commons.logging.LogFactory;
|
|
|
import org.springframework.beans.BeansException;
|
|
|
import org.springframework.beans.PropertyValue;
|
|
|
+import org.springframework.beans.factory.BeanFactory;
|
|
|
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
|
|
import org.springframework.beans.factory.config.BeanDefinition;
|
|
|
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
|
|
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
|
|
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
|
|
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
|
|
import org.springframework.beans.factory.support.RootBeanDefinition;
|
|
|
-import org.springframework.core.OrderComparator;
|
|
|
import org.springframework.core.Ordered;
|
|
|
-import org.springframework.security.concurrent.ConcurrentSessionFilter;
|
|
|
-import org.springframework.security.context.HttpSessionContextIntegrationFilter;
|
|
|
import org.springframework.security.ui.AbstractProcessingFilter;
|
|
|
import org.springframework.security.ui.AuthenticationEntryPoint;
|
|
|
import org.springframework.security.ui.basicauth.BasicProcessingFilter;
|
|
|
import org.springframework.security.ui.rememberme.RememberMeServices;
|
|
|
import org.springframework.security.userdetails.UserDetailsByNameServiceWrapper;
|
|
|
-import org.springframework.security.util.FilterChainProxy;
|
|
|
import org.springframework.util.Assert;
|
|
|
|
|
|
/**
|
|
|
- * Responsible for tying up the HTTP security configuration - building ordered filter stack and linking up
|
|
|
- * with other beans.
|
|
|
+ * Responsible for tying up the HTTP security configuration once all the beans are registered.
|
|
|
+ * This class does not actually instantiate any beans (for example, it should not call {@link BeanFactory#getBean(String)}).
|
|
|
+ * All the wiring up should be done using bean definitions or bean references to avoid. This approach should avoid any
|
|
|
+ * conflict with other processors.
|
|
|
*
|
|
|
* @author Luke Taylor
|
|
|
* @author Ben Alex
|
|
@@ -46,12 +41,7 @@ public class HttpSecurityConfigPostProcessor implements BeanFactoryPostProcessor
|
|
|
injectUserDetailsServiceIntoRememberMeServices(beanFactory);
|
|
|
injectUserDetailsServiceIntoX509Provider(beanFactory);
|
|
|
injectUserDetailsServiceIntoOpenIDProvider(beanFactory);
|
|
|
-
|
|
|
injectAuthenticationEntryPointIntoExceptionTranslationFilter(beanFactory);
|
|
|
-
|
|
|
- injectRememberMeServicesIntoFiltersRequiringIt(beanFactory);
|
|
|
-
|
|
|
- configureFilterChain(beanFactory);
|
|
|
}
|
|
|
|
|
|
private void injectUserDetailsServiceIntoRememberMeServices(ConfigurableListableBeanFactory bf) {
|
|
@@ -80,10 +70,10 @@ public class HttpSecurityConfigPostProcessor implements BeanFactoryPostProcessor
|
|
|
PropertyValue pv = x509AuthProvider.getPropertyValues().getPropertyValue("preAuthenticatedUserDetailsService");
|
|
|
|
|
|
if (pv == null) {
|
|
|
- UserDetailsByNameServiceWrapper preAuthUserService = new UserDetailsByNameServiceWrapper();
|
|
|
- preAuthUserService.setUserDetailsService(ConfigUtils.getUserDetailsService(bf));
|
|
|
+ BeanDefinitionBuilder preAuthUserService = BeanDefinitionBuilder.rootBeanDefinition(UserDetailsByNameServiceWrapper.class);
|
|
|
+ preAuthUserService.addPropertyValue("userDetailsService", ConfigUtils.getUserDetailsService(bf));
|
|
|
x509AuthProvider.getPropertyValues().addPropertyValue("preAuthenticatedUserDetailsService",
|
|
|
- preAuthUserService);
|
|
|
+ preAuthUserService.getBeanDefinition());
|
|
|
} else {
|
|
|
RootBeanDefinition preAuthUserService = (RootBeanDefinition) pv.getValue();
|
|
|
Object userService =
|
|
@@ -130,156 +120,34 @@ public class HttpSecurityConfigPostProcessor implements BeanFactoryPostProcessor
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Sets the remember-me services, if required, on any instances of AbstractProcessingFilter and
|
|
|
- * BasicProcessingFilter.
|
|
|
- */
|
|
|
- private void injectRememberMeServicesIntoFiltersRequiringIt(ConfigurableListableBeanFactory beanFactory) {
|
|
|
- Map beans = beanFactory.getBeansOfType(RememberMeServices.class);
|
|
|
-
|
|
|
- RememberMeServices rememberMeServices = null;
|
|
|
-
|
|
|
- if(beans.size() == 0) {
|
|
|
- logger.debug("No RememberMeServices configured");
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if (beans.size() == 1) {
|
|
|
- rememberMeServices = (RememberMeServices) beans.values().toArray()[0];
|
|
|
- } else {
|
|
|
- throw new SecurityConfigurationException("More than one RememberMeServices bean found.");
|
|
|
- }
|
|
|
-
|
|
|
- if (rememberMeServices == null) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // Address AbstractProcessingFilter instances
|
|
|
- Iterator filters = beanFactory.getBeansOfType(AbstractProcessingFilter.class).values().iterator();
|
|
|
-
|
|
|
- while (filters.hasNext()) {
|
|
|
- AbstractProcessingFilter filter = (AbstractProcessingFilter) filters.next();
|
|
|
-
|
|
|
- logger.info("Using RememberMeServices " + rememberMeServices + " with filter " + filter);
|
|
|
- filter.setRememberMeServices(rememberMeServices);
|
|
|
- }
|
|
|
-
|
|
|
- // Address BasicProcessingFilter instance, if it exists
|
|
|
- // NB: For remember-me to be sent back, a user must submit a "_spring_security_remember_me" with their login request.
|
|
|
- // Most of the time a user won't present such a parameter with their BASIC authentication request.
|
|
|
- // In the future we might support setting the AbstractRememberMeServices.alwaysRemember = true, but I am reluctant to
|
|
|
- // do so because it seems likely to lead to lower security for 99.99% of users if they set the property to true.
|
|
|
- if (beanFactory.containsBean(BeanIds.BASIC_AUTHENTICATION_FILTER)) {
|
|
|
- BasicProcessingFilter filter = (BasicProcessingFilter) beanFactory.getBean(BeanIds.BASIC_AUTHENTICATION_FILTER);
|
|
|
-
|
|
|
- logger.info("Using RememberMeServices " + rememberMeServices + " with filter " + filter);
|
|
|
- filter.setRememberMeServices(rememberMeServices);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Selects the entry point that should be used in ExceptionTranslationFilter. Strategy is
|
|
|
- *
|
|
|
- * <ol>
|
|
|
- * <li>If only one, use that one.</li>
|
|
|
- * <li>If more than one, use the form login entry point (if form login is being used), then try basic</li>
|
|
|
- * <li>If still null, throw an exception (for now).</li>
|
|
|
- * </ol>
|
|
|
+ * Selects the entry point that should be used in ExceptionTranslationFilter. If an entry point has been
|
|
|
+ * set during parsing of form, openID and basic authentication information, or via a custom reference
|
|
|
+ * (using <tt>custom-entry-point</tt>, then that will be used. Otherwise there
|
|
|
+ * must be a single entry point bean and that will be used.
|
|
|
+ *
|
|
|
+ * Todo: this could probably be more easily be done in a BeanPostProcessor for ExceptionTranslationFilter.
|
|
|
*
|
|
|
*/
|
|
|
private void injectAuthenticationEntryPointIntoExceptionTranslationFilter(ConfigurableListableBeanFactory beanFactory) {
|
|
|
- logger.info("Selecting AuthenticationEntryPoint for use in ExceptionTranslationFilter");
|
|
|
-
|
|
|
+ logger.info("Selecting AuthenticationEntryPoint for use in ExceptionTranslationFilter");
|
|
|
+
|
|
|
BeanDefinition etf =
|
|
|
beanFactory.getBeanDefinition(BeanIds.EXCEPTION_TRANSLATION_FILTER);
|
|
|
- Map entryPointMap = beanFactory.getBeansOfType(AuthenticationEntryPoint.class);
|
|
|
- List entryPoints = new ArrayList(entryPointMap.values());
|
|
|
-
|
|
|
- Assert.isTrue(entryPoints.size() > 0, "No AuthenticationEntryPoint instances defined");
|
|
|
-
|
|
|
- AuthenticationEntryPoint mainEntryPoint;
|
|
|
-
|
|
|
- if (entryPoints.size() == 1) {
|
|
|
- mainEntryPoint = (AuthenticationEntryPoint) entryPoints.get(0);
|
|
|
+
|
|
|
+ String entryPoint = null;
|
|
|
+
|
|
|
+ if (beanFactory.containsBean(BeanIds.MAIN_ENTRY_POINT)) {
|
|
|
+ entryPoint = BeanIds.MAIN_ENTRY_POINT;
|
|
|
+ logger.info("Using main configured AuthenticationEntryPoint set to " + BeanIds.MAIN_ENTRY_POINT);
|
|
|
} else {
|
|
|
- mainEntryPoint = (AuthenticationEntryPoint) beanFactory.getBean(BeanIds.MAIN_ENTRY_POINT);
|
|
|
-
|
|
|
- if (mainEntryPoint == null) {
|
|
|
- mainEntryPoint = (AuthenticationEntryPoint) entryPointMap.get(BeanIds.FORM_LOGIN_ENTRY_POINT);
|
|
|
- }
|
|
|
-
|
|
|
- if (mainEntryPoint == null) {
|
|
|
- mainEntryPoint = (AuthenticationEntryPoint) entryPointMap.get(BeanIds.BASIC_AUTHENTICATION_ENTRY_POINT);
|
|
|
- if (mainEntryPoint == null) {
|
|
|
- throw new SecurityConfigurationException("Failed to resolve authentication entry point");
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- logger.info("Main AuthenticationEntryPoint set to " + mainEntryPoint);
|
|
|
-
|
|
|
- etf.getPropertyValues().addPropertyValue("authenticationEntryPoint", mainEntryPoint);
|
|
|
- }
|
|
|
-
|
|
|
- private void configureFilterChain(ConfigurableListableBeanFactory beanFactory) {
|
|
|
- FilterChainProxy filterChainProxy =
|
|
|
- (FilterChainProxy) beanFactory.getBean(BeanIds.FILTER_CHAIN_PROXY);
|
|
|
- // Set the default match
|
|
|
- List defaultFilterChain = orderFilters(beanFactory);
|
|
|
-
|
|
|
- // Note that this returns a copy
|
|
|
- Map filterMap = filterChainProxy.getFilterChainMap();
|
|
|
-
|
|
|
- String allUrlsMatch = filterChainProxy.getMatcher().getUniversalMatchPattern();
|
|
|
-
|
|
|
- filterMap.put(allUrlsMatch, defaultFilterChain);
|
|
|
-
|
|
|
- filterChainProxy.setFilterChainMap(filterMap);
|
|
|
-
|
|
|
- Map sessionFilters = beanFactory.getBeansOfType(ConcurrentSessionFilter.class);
|
|
|
-
|
|
|
- if (!sessionFilters.isEmpty()) {
|
|
|
- logger.info("Concurrent session filter in use, setting 'forceEagerSessionCreation' to true");
|
|
|
- HttpSessionContextIntegrationFilter scif = (HttpSessionContextIntegrationFilter)
|
|
|
- beanFactory.getBean(BeanIds.HTTP_SESSION_CONTEXT_INTEGRATION_FILTER);
|
|
|
- scif.setForceEagerSessionCreation(true);
|
|
|
+ String[] entryPoints = beanFactory.getBeanNamesForType(AuthenticationEntryPoint.class);
|
|
|
+ Assert.isTrue(entryPoints.length != 0, "No AuthenticationEntryPoint instances defined");
|
|
|
+ Assert.isTrue(entryPoints.length == 1, "More than one AuthenticationEntryPoint defined in context");
|
|
|
+ entryPoint = entryPoints[0];
|
|
|
}
|
|
|
-
|
|
|
- logger.info("Configured filter chain(s): " + filterChainProxy);
|
|
|
- }
|
|
|
-
|
|
|
- private List orderFilters(ConfigurableListableBeanFactory beanFactory) {
|
|
|
- Map filters = beanFactory.getBeansOfType(Filter.class);
|
|
|
-
|
|
|
- Assert.notEmpty(filters, "No filters found in app context!");
|
|
|
-
|
|
|
- Iterator ids = filters.keySet().iterator();
|
|
|
-
|
|
|
- List orderedFilters = new ArrayList();
|
|
|
-
|
|
|
- while (ids.hasNext()) {
|
|
|
- String id = (String) ids.next();
|
|
|
- Filter filter = (Filter) filters.get(id);
|
|
|
-
|
|
|
- if (filter instanceof FilterChainProxy) {
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- // Filters must be Spring security filters or wrapped using <custom-filter>
|
|
|
- if (!filter.getClass().getName().startsWith("org.springframework.security")) {
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- if (!(filter instanceof Ordered)) {
|
|
|
- logger.info("Filter " + id + " doesn't implement the Ordered interface, skipping it.");
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- orderedFilters.add(filter);
|
|
|
- }
|
|
|
-
|
|
|
- Collections.sort(orderedFilters, new OrderComparator());
|
|
|
-
|
|
|
- return orderedFilters;
|
|
|
+
|
|
|
+ logger.info("Using bean '" + entryPoint + "' as the entry point.");
|
|
|
+ etf.getPropertyValues().addPropertyValue("authenticationEntryPoint", new RuntimeBeanReference(entryPoint));
|
|
|
}
|
|
|
|
|
|
public int getOrder() {
|