From d5bf5d7adca6cedd2388b0597133b3a26b07c747 Mon Sep 17 00:00:00 2001 From: Luke Taylor Date: Fri, 26 Jun 2009 12:47:03 +0000 Subject: [PATCH] SEC-1186: validator for filter chain beans --- .../config/DefaultFilterChainValidator.java | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 config/src/main/java/org/springframework/security/config/DefaultFilterChainValidator.java diff --git a/config/src/main/java/org/springframework/security/config/DefaultFilterChainValidator.java b/config/src/main/java/org/springframework/security/config/DefaultFilterChainValidator.java new file mode 100644 index 0000000000..43abaaef50 --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/DefaultFilterChainValidator.java @@ -0,0 +1,135 @@ +package org.springframework.security.config; + +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.security.access.ConfigAttribute; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.web.access.ExceptionTranslationFilter; +import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource; +import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; +import org.springframework.security.web.authentication.AnonymousProcessingFilter; +import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationProcessingFilter; +import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter; +import org.springframework.security.web.authentication.www.BasicProcessingFilter; +import org.springframework.security.web.context.SecurityContextPersistenceFilter; +import org.springframework.security.web.session.SessionFixationProtectionFilter; +import org.springframework.security.web.wrapper.SecurityContextHolderAwareRequestFilter; + +public class DefaultFilterChainValidator implements FilterChainProxy.FilterChainValidator{ + private Log logger = LogFactory.getLog(getClass()); + + public void validate(FilterChainProxy fcp) { + Map> filterChainMap = fcp.getFilterChainMap(); + for(String pattern : fcp.getFilterChainMap().keySet()) { + List filters = filterChainMap.get(pattern); + checkFilterStack(filters); + } + + checkLoginPageIsntProtected(fcp, filterChainMap.get(fcp.getMatcher().getUniversalMatchPattern())); + } + + private Object getFilter(Class type, List filters) { + + for (Filter f : filters) { + if (type.isAssignableFrom(f.getClass())) { + return f; + } + } + + return null; + } + + /** + * Checks the filter list for possible errors and logs them + */ + private void checkFilterStack(List filters) { + checkForDuplicates(SecurityContextPersistenceFilter.class, filters); + checkForDuplicates(UsernamePasswordAuthenticationProcessingFilter.class, filters); + checkForDuplicates(SessionFixationProtectionFilter.class, filters); + checkForDuplicates(BasicProcessingFilter.class, filters); + checkForDuplicates(SecurityContextHolderAwareRequestFilter.class, filters); + checkForDuplicates(ExceptionTranslationFilter.class, filters); + checkForDuplicates(FilterSecurityInterceptor.class, filters); + } + + private void checkForDuplicates(Class clazz, List filters) { + for (int i=0; i < filters.size(); i++) { + Filter f1 = filters.get(i); + if (clazz.isAssignableFrom(f1.getClass())) { + // Found the first one, check remaining for another + for (int j=i+1; j < filters.size(); j++) { + Filter f2 = filters.get(j); + if (clazz.isAssignableFrom(f2.getClass())) { + logger.warn("Possible error: Filters at position " + i + " and " + j + " are both " + + "instances of " + clazz.getName()); + return; + } + } + } + } + } + + /* Checks for the common error of having a login page URL protected by the security interceptor */ + private void checkLoginPageIsntProtected(FilterChainProxy fcp, List defaultFilters) { + ExceptionTranslationFilter etf = (ExceptionTranslationFilter)getFilter(ExceptionTranslationFilter.class, defaultFilters); + + if (etf.getAuthenticationEntryPoint() instanceof LoginUrlAuthenticationEntryPoint) { + String loginPage = + ((LoginUrlAuthenticationEntryPoint)etf.getAuthenticationEntryPoint()).getLoginFormUrl(); + List filters = fcp.getFilters(loginPage); + logger.info("Checking whether login URL '" + loginPage + "' is accessible with your configuration"); + + if (filters == null || filters.isEmpty()) { + logger.debug("Filter chain is empty for the login page"); + return; + } + + if (getFilter(DefaultLoginPageGeneratingFilter.class, filters) != null) { + logger.debug("Default generated login page is in use"); + return; + } + + FilterSecurityInterceptor fsi = (FilterSecurityInterceptor) getFilter(FilterSecurityInterceptor.class, filters); + DefaultFilterInvocationSecurityMetadataSource fids = + (DefaultFilterInvocationSecurityMetadataSource) fsi.getSecurityMetadataSource(); + List attributes = fids.lookupAttributes(loginPage, "POST"); + + if (attributes == null) { + logger.debug("No access attributes defined for login page URL"); + if (fsi.isRejectPublicInvocations()) { + logger.warn("FilterSecurityInterceptor is configured to reject public invocations." + + " Your login page may not be accessible."); + } + return; + } + + AnonymousProcessingFilter anonPF = (AnonymousProcessingFilter) getFilter(AnonymousProcessingFilter.class, filters); + if (anonPF == null) { + logger.warn("The login page is being protected by the filter chain, but you don't appear to have" + + " anonymous authentication enabled. This is almost certainly an error."); + return; + } + + // Simulate an anonymous access with the supplied attributes. + AnonymousAuthenticationToken token = new AnonymousAuthenticationToken("key", anonPF.getUserAttribute().getPassword(), + anonPF.getUserAttribute().getAuthorities()); + try { + fsi.getAccessDecisionManager().decide(token, new Object(), fids.lookupAttributes(loginPage, "POST")); + } catch (Exception e) { + logger.warn("Anonymous access to the login page doesn't appear to be enabled. This is almost certainly " + + "an error. Please check your configuration allows unauthenticated access to the configured " + + "login page. (Simulated access was rejected: " + e + ")"); + } + } + } + + + +}