1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.springframework.security.util;
17
18 import org.apache.commons.logging.Log;
19 import org.apache.commons.logging.LogFactory;
20 import org.springframework.beans.BeansException;
21 import org.springframework.beans.factory.InitializingBean;
22 import org.springframework.context.ApplicationContext;
23 import org.springframework.context.ApplicationContextAware;
24 import org.springframework.security.intercept.web.*;
25 import org.springframework.util.Assert;
26 import org.springframework.web.filter.DelegatingFilterProxy;
27
28 import javax.servlet.*;
29 import java.io.IOException;
30 import java.util.*;
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96 public class FilterChainProxy implements Filter, InitializingBean, ApplicationContextAware {
97
98
99 private static final Log logger = LogFactory.getLog(FilterChainProxy.class);
100 public static final String TOKEN_NONE = "#NONE#";
101
102
103
104 private ApplicationContext applicationContext;
105
106 private Map uncompiledFilterChainMap;
107
108 private Map filterChainMap;
109 private UrlMatcher matcher = new AntUrlPathMatcher();
110 private boolean stripQueryStringFromUrls = true;
111 private DefaultFilterInvocationDefinitionSource fids;
112
113
114
115 public void afterPropertiesSet() throws Exception {
116
117 if (fids != null) {
118 Assert.isNull(uncompiledFilterChainMap, "Set the filterChainMap or FilterInvocationDefinitionSource but not both");
119 FIDSToFilterChainMapConverter converter = new FIDSToFilterChainMapConverter(fids, applicationContext);
120 setMatcher(converter.getMatcher());
121 setFilterChainMap(converter.getFilterChainMap());
122 fids = null;
123 }
124
125 Assert.notNull(uncompiledFilterChainMap, "filterChainMap must be set");
126
127 }
128
129 public void init(FilterConfig filterConfig) throws ServletException {
130 Filter[] filters = obtainAllDefinedFilters();
131
132 for (int i = 0; i < filters.length; i++) {
133 if (filters[i] != null) {
134 if (logger.isDebugEnabled()) {
135 logger.debug("Initializing Filter defined in ApplicationContext: '" + filters[i].toString() + "'");
136 }
137
138 filters[i].init(filterConfig);
139 }
140 }
141 }
142
143 public void destroy() {
144 Filter[] filters = obtainAllDefinedFilters();
145
146 for (int i = 0; i < filters.length; i++) {
147 if (filters[i] != null) {
148 if (logger.isDebugEnabled()) {
149 logger.debug("Destroying Filter defined in ApplicationContext: '" + filters[i].toString() + "'");
150 }
151
152 filters[i].destroy();
153 }
154 }
155 }
156
157 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
158 throws IOException, ServletException {
159
160 FilterInvocation fi = new FilterInvocation(request, response, chain);
161 List filters = getFilters(fi.getRequestUrl());
162
163 if (filters == null || filters.size() == 0) {
164 if (logger.isDebugEnabled()) {
165 logger.debug(fi.getRequestUrl() +
166 filters == null ? " has no matching filters" : " has an empty filter list");
167 }
168
169 chain.doFilter(request, response);
170
171 return;
172 }
173
174 VirtualFilterChain virtualFilterChain = new VirtualFilterChain(fi, filters);
175 virtualFilterChain.doFilter(fi.getRequest(), fi.getResponse());
176 }
177
178
179
180
181
182
183
184 public List getFilters(String url) {
185 if (stripQueryStringFromUrls) {
186
187 int firstQuestionMarkIndex = url.indexOf("?");
188
189 if (firstQuestionMarkIndex != -1) {
190 url = url.substring(0, firstQuestionMarkIndex);
191 }
192 }
193
194
195 Iterator filterChains = filterChainMap.entrySet().iterator();
196
197 while (filterChains.hasNext()) {
198 Map.Entry entry = (Map.Entry) filterChains.next();
199 Object path = entry.getKey();
200
201 if (matcher.requiresLowerCaseUrl()) {
202 url = url.toLowerCase();
203
204 if (logger.isDebugEnabled()) {
205 logger.debug("Converted URL to lowercase, from: '" + url + "'; to: '" + url + "'");
206 }
207 }
208
209 boolean matched = matcher.pathMatchesUrl(path, url);
210
211 if (logger.isDebugEnabled()) {
212 logger.debug("Candidate is: '" + url + "'; pattern is " + path + "; matched=" + matched);
213 }
214
215 if (matched) {
216 return (List) entry.getValue();
217 }
218 }
219
220 return null;
221 }
222
223
224
225
226
227
228
229
230
231
232
233 protected Filter[] obtainAllDefinedFilters() {
234 Set allFilters = new LinkedHashSet();
235
236 Iterator it = filterChainMap.values().iterator();
237
238 while (it.hasNext()) {
239 allFilters.addAll((List) it.next());
240 }
241
242 return (Filter[]) new ArrayList(allFilters).toArray(new Filter[0]);
243 }
244
245 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
246 this.applicationContext = applicationContext;
247 }
248
249
250
251
252
253 public void setFilterInvocationDefinitionSource(FilterInvocationDefinitionSource fids) {
254 Assert.isInstanceOf(DefaultFilterInvocationDefinitionSource.class, fids,
255 "Must be a DefaultFilterInvocationDefinitionSource");
256 this.fids = (DefaultFilterInvocationDefinitionSource) fids;
257 }
258
259
260
261
262
263
264
265
266
267
268
269
270
271 public void setFilterChainMap(Map filterChainMap) {
272 uncompiledFilterChainMap = new LinkedHashMap(filterChainMap);
273 checkPathOrder();
274 createCompiledMap();
275 }
276
277 private void checkPathOrder() {
278
279 String[] paths = (String[]) uncompiledFilterChainMap.keySet().toArray(new String[0]);
280 String universalMatch = matcher.getUniversalMatchPattern();
281
282 for (int i=0; i < paths.length-1; i++) {
283 if (paths[i].equals(universalMatch)) {
284 throw new IllegalArgumentException("A universal match pattern " + universalMatch + " is defined " +
285 " before other patterns in the filter chain, causing them to be ignored. Please check the " +
286 "ordering in your <security:http> namespace or FilterChainProxy bean configuration");
287 }
288 }
289 }
290
291 private void createCompiledMap() {
292 Iterator paths = uncompiledFilterChainMap.keySet().iterator();
293 filterChainMap = new LinkedHashMap(uncompiledFilterChainMap.size());
294
295 while (paths.hasNext()) {
296 Object path = paths.next();
297 Assert.isInstanceOf(String.class, path, "Path pattern must be a String");
298 Object compiledPath = matcher.compile((String)path);
299 Object filters = uncompiledFilterChainMap.get(path);
300
301 Assert.isInstanceOf(List.class, filters);
302
303 Iterator filterIterator = ((List)filters).iterator();
304
305 while (filterIterator.hasNext()) {
306 Object filter = filterIterator.next();
307 Assert.isInstanceOf(Filter.class, filter, "Objects in filter chain must be of type Filter. ");
308 }
309
310 filterChainMap.put(compiledPath, filters);
311 }
312 }
313
314
315
316
317
318
319
320
321 public Map getFilterChainMap() {
322 return new LinkedHashMap(uncompiledFilterChainMap);
323 }
324
325 public void setMatcher(UrlMatcher matcher) {
326 this.matcher = matcher;
327 }
328
329 public UrlMatcher getMatcher() {
330 return matcher;
331 }
332
333
334
335
336
337 public void setStripQueryStringFromUrls(boolean stripQueryStringFromUrls) {
338 this.stripQueryStringFromUrls = stripQueryStringFromUrls;
339 }
340
341 public String toString() {
342 StringBuffer sb = new StringBuffer();
343 sb.append("FilterChainProxy[");
344 sb.append(" UrlMatcher = ").append(matcher);
345 sb.append("; Filter Chains: ");
346 sb.append(uncompiledFilterChainMap);
347 sb.append("]");
348
349 return sb.toString();
350 }
351
352
353
354
355
356
357
358
359
360 private static class VirtualFilterChain implements FilterChain {
361 private FilterInvocation fi;
362 private List additionalFilters;
363 private int currentPosition = 0;
364
365 private VirtualFilterChain(FilterInvocation filterInvocation, List additionalFilters) {
366 this.fi = filterInvocation;
367 this.additionalFilters = additionalFilters;
368 }
369
370 public void doFilter(ServletRequest request, ServletResponse response)
371 throws IOException, ServletException {
372 if (currentPosition == additionalFilters.size()) {
373 if (logger.isDebugEnabled()) {
374 logger.debug(fi.getRequestUrl()
375 + " reached end of additional filter chain; proceeding with original chain");
376 }
377
378 fi.getChain().doFilter(request, response);
379 } else {
380 currentPosition++;
381
382 Filter nextFilter = (Filter) additionalFilters.get(currentPosition - 1);
383
384 if (logger.isDebugEnabled()) {
385 logger.debug(fi.getRequestUrl() + " at position " + currentPosition + " of "
386 + additionalFilters.size() + " in additional filter chain; firing Filter: '"
387 + nextFilter + "'");
388 }
389
390 nextFilter.doFilter(request, response, this);
391 }
392 }
393 }
394
395 }