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
38
39
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
77 Filter filter = unwrapFilter(filters.get(i));
78 logger.info("[" + i + "] - " + filter);
79 filters.set(i, filter);
80 }
81
82 checkFilterStack(filters);
83
84
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
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
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
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
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
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 }