View Javadoc

1   package org.springframework.security.config;
2   
3   import java.util.ArrayList;
4   import java.util.Collections;
5   import java.util.List;
6   import java.util.Map;
7   
8   import javax.servlet.Filter;
9   
10  import org.apache.commons.logging.Log;
11  import org.apache.commons.logging.LogFactory;
12  import org.springframework.beans.BeansException;
13  import org.springframework.beans.factory.BeanFactory;
14  import org.springframework.beans.factory.BeanFactoryAware;
15  import org.springframework.beans.factory.ListableBeanFactory;
16  import org.springframework.beans.factory.config.BeanPostProcessor;
17  import org.springframework.core.OrderComparator;
18  import org.springframework.core.Ordered;
19  import org.springframework.security.ConfigAttributeDefinition;
20  import org.springframework.security.config.ConfigUtils.FilterChainList;
21  import org.springframework.security.context.HttpSessionContextIntegrationFilter;
22  import org.springframework.security.intercept.web.DefaultFilterInvocationDefinitionSource;
23  import org.springframework.security.intercept.web.FilterSecurityInterceptor;
24  import org.springframework.security.providers.anonymous.AnonymousAuthenticationToken;
25  import org.springframework.security.providers.anonymous.AnonymousProcessingFilter;
26  import org.springframework.security.ui.ExceptionTranslationFilter;
27  import org.springframework.security.ui.SessionFixationProtectionFilter;
28  import org.springframework.security.ui.basicauth.BasicProcessingFilter;
29  import org.springframework.security.ui.webapp.AuthenticationProcessingFilter;
30  import org.springframework.security.ui.webapp.AuthenticationProcessingFilterEntryPoint;
31  import org.springframework.security.ui.webapp.DefaultLoginPageGeneratingFilter;
32  import org.springframework.security.util.FilterChainProxy;
33  import org.springframework.security.wrapper.SecurityContextHolderAwareRequestFilter;
34  
35  /**
36   * 
37   * @author Luke Taylor
38   * @version $Id: FilterChainProxyPostProcessor.java 3149 2008-06-19 15:21:03Z luke_t $
39   * @since 2.0
40   */
41  public class FilterChainProxyPostProcessor implements BeanPostProcessor, BeanFactoryAware {
42      private Log logger = LogFactory.getLog(getClass());
43      
44      private ListableBeanFactory beanFactory;
45      
46      public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
47          if(!BeanIds.FILTER_CHAIN_PROXY.equals(beanName)) {
48              return bean;
49          }
50          
51          FilterChainProxy filterChainProxy = (FilterChainProxy) bean;
52          FilterChainList filterList = (FilterChainList) beanFactory.getBean(BeanIds.FILTER_LIST);
53          
54          List filters = new ArrayList(filterList.getFilters());
55          Collections.sort(filters, new OrderComparator());
56          
57          logger.info("Checking sorted filter chain: " + filters);
58          
59          for(int i=0; i < filters.size(); i++) {
60              Ordered filter = (Ordered)filters.get(i);
61  
62              if (i > 0) {
63                  Ordered previous = (Ordered)filters.get(i-1);
64                  if (filter.getOrder() == previous.getOrder()) {
65                      throw new SecurityConfigurationException("Filters '" + unwrapFilter(filter) + "' and '" + 
66                              unwrapFilter(previous) + "' have the same 'order' value. When using custom filters, " +
67                                      "please make sure the positions do not conflict with default filters. " +
68                                      "Alternatively you can disable the default filters by removing the corresponding " +
69                                      "child elements from <http> and avoiding the use of <http auto-config='true'>.");
70                  }
71              }
72          }
73  
74          logger.info("Filter chain...");        
75          for (int i=0; i < filters.size(); i++) {
76          // Remove the ordered wrapper from the filter and put it back in the chain at the same position.
77              Filter filter = unwrapFilter(filters.get(i));
78              logger.info("[" + i + "] - " + filter);            
79              filters.set(i, filter);
80          }
81          
82          checkFilterStack(filters);
83          
84          // Note that this returns a copy
85          Map filterMap = filterChainProxy.getFilterChainMap();
86          filterMap.put(filterChainProxy.getMatcher().getUniversalMatchPattern(), filters);
87          filterChainProxy.setFilterChainMap(filterMap);
88          
89          checkLoginPageIsntProtected(filterChainProxy);
90          
91          logger.info("FilterChainProxy: " + filterChainProxy);
92  
93          return bean;
94      }
95      
96      /**
97       * Checks the filter list for possible errors and logs them
98       */
99      private void checkFilterStack(List filters) {
100         checkForDuplicates(HttpSessionContextIntegrationFilter.class, filters);
101         checkForDuplicates(AuthenticationProcessingFilter.class, filters);
102         checkForDuplicates(SessionFixationProtectionFilter.class, filters);
103         checkForDuplicates(BasicProcessingFilter.class, filters);
104         checkForDuplicates(SecurityContextHolderAwareRequestFilter.class, filters);
105         checkForDuplicates(ExceptionTranslationFilter.class, filters);
106         checkForDuplicates(FilterSecurityInterceptor.class, filters);
107     }
108     
109     private void checkForDuplicates(Class clazz, List filters) {
110         for (int i=0; i < filters.size(); i++) {
111             Filter f1 = (Filter)filters.get(i);
112             if (clazz.isAssignableFrom(f1.getClass())) {
113                 // Found the first one, check remaining for another
114                 for (int j=i+1; j < filters.size(); j++) {
115                     Filter f2 = (Filter)filters.get(j);
116                     if (clazz.isAssignableFrom(f2.getClass())) {
117                         logger.warn("Possible error: Filters at position " + i + " and " + j + " are both " +
118                                 "instances of " + clazz.getName());
119                         return;
120                     }
121                 }
122             }
123         }
124     }
125     
126     /* Checks for the common error of having a login page URL protected by the security interceptor */
127     private void checkLoginPageIsntProtected(FilterChainProxy fcp) {
128         ExceptionTranslationFilter etf = (ExceptionTranslationFilter) beanFactory.getBean(BeanIds.EXCEPTION_TRANSLATION_FILTER);
129         
130         if (etf.getAuthenticationEntryPoint() instanceof AuthenticationProcessingFilterEntryPoint) {
131             String loginPage = 
132                 ((AuthenticationProcessingFilterEntryPoint)etf.getAuthenticationEntryPoint()).getLoginFormUrl();
133             List filters = fcp.getFilters(loginPage);
134             logger.info("Checking whether login URL '" + loginPage + "' is accessible with your configuration");
135                         
136             if (filters == null || filters.isEmpty()) {
137                 logger.debug("Filter chain is empty for the login page");
138                 return;
139             }
140             
141             if (loginPage.equals(DefaultLoginPageGeneratingFilter.DEFAULT_LOGIN_PAGE_URL) &&
142                     beanFactory.containsBean(BeanIds.DEFAULT_LOGIN_PAGE_GENERATING_FILTER)) {
143                 logger.debug("Default generated login page is in use");
144                 return;
145             }
146             
147             FilterSecurityInterceptor fsi = 
148                     ((FilterSecurityInterceptor)beanFactory.getBean(BeanIds.FILTER_SECURITY_INTERCEPTOR));
149             DefaultFilterInvocationDefinitionSource fids = 
150                     (DefaultFilterInvocationDefinitionSource) fsi.getObjectDefinitionSource();
151             ConfigAttributeDefinition cad = fids.lookupAttributes(loginPage, "POST");
152             
153             if (cad == null) {
154                 logger.debug("No access attributes defined for login page URL");
155                 if (fsi.isRejectPublicInvocations()) {
156                     logger.warn("FilterSecurityInterceptor is configured to reject public invocations." +
157                             " Your login page may not be accessible.");
158                 }
159                 return;
160             }
161 
162             if (!beanFactory.containsBean(BeanIds.ANONYMOUS_PROCESSING_FILTER)) {
163                 logger.warn("The login page is being protected by the filter chain, but you don't appear to have" +
164                         " anonymous authentication enabled. This is almost certainly an error.");
165                 return;
166             }
167             
168             // Simulate an anonymous access with the supplied attributes.
169             AnonymousProcessingFilter anonPF = (AnonymousProcessingFilter) beanFactory.getBean(BeanIds.ANONYMOUS_PROCESSING_FILTER);
170             AnonymousAuthenticationToken token = 
171                     new AnonymousAuthenticationToken("key", anonPF.getUserAttribute().getPassword(), 
172                             anonPF.getUserAttribute().getAuthorities());
173             try {
174                 fsi.getAccessDecisionManager().decide(token, new Object(), cad);
175             } catch (Exception e) {
176                 logger.warn("Anonymous access to the login page doesn't appear to be enabled. This is almost certainly " +
177                         "an error. Please check your configuration allows unauthenticated access to the configured " +
178                         "login page. (Simulated access was rejected: " + e + ")");
179             }
180         }
181     }
182     
183     /** 
184      * Returns the delegate filter of a wrapper, or the unchanged filter if it isn't wrapped. 
185      */
186     private Filter unwrapFilter(Object filter) {
187         if (filter instanceof OrderedFilterBeanDefinitionDecorator.OrderedFilterDecorator) {
188             return ((OrderedFilterBeanDefinitionDecorator.OrderedFilterDecorator)filter).getDelegate();
189         }
190         
191         return (Filter) filter;
192     }
193 
194     public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
195         return bean;
196     }
197 
198     public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
199         this.beanFactory = (ListableBeanFactory) beanFactory;
200     }
201 }