001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019 package org.apache.shiro.web.filter;
020
021 import org.apache.shiro.util.AntPathMatcher;
022 import org.apache.shiro.util.PatternMatcher;
023 import org.apache.shiro.util.StringUtils;
024 import org.apache.shiro.web.servlet.AdviceFilter;
025 import org.apache.shiro.web.util.WebUtils;
026 import org.slf4j.Logger;
027 import org.slf4j.LoggerFactory;
028
029 import javax.servlet.Filter;
030 import javax.servlet.ServletRequest;
031 import javax.servlet.ServletResponse;
032 import java.util.LinkedHashMap;
033 import java.util.Map;
034
035 import static org.apache.shiro.util.StringUtils.split;
036
037 /**
038 * <p>Base class for Filters that will process only specified paths and allow all others to pass through.</p>
039 *
040 * @since 0.9
041 */
042 public abstract class PathMatchingFilter extends AdviceFilter implements PathConfigProcessor {
043
044 /**
045 * Log available to this class only
046 */
047 private static final Logger log = LoggerFactory.getLogger(PathMatchingFilter.class);
048
049 /**
050 * PatternMatcher used in determining which paths to react to for a given request.
051 */
052 protected PatternMatcher pathMatcher = new AntPathMatcher();
053
054 /**
055 * A collection of path-to-config entries where the key is a path which this filter should process and
056 * the value is the (possibly null) configuration element specific to this Filter for that specific path.
057 * <p/>
058 * <p>To put it another way, the keys are the paths (urls) that this Filter will process.
059 * <p>The values are filter-specific data that this Filter should use when processing the corresponding
060 * key (path). The values can be null if no Filter-specific config was specified for that url.
061 */
062 protected Map<String, Object> appliedPaths = new LinkedHashMap<String, Object>();
063
064 /**
065 * Splits any comma-delmited values that might be found in the <code>config</code> argument and sets the resulting
066 * <code>String[]</code> array on the <code>appliedPaths</code> internal Map.
067 * <p/>
068 * That is:
069 * <pre><code>
070 * String[] values = null;
071 * if (config != null) {
072 * values = split(config);
073 * }
074 * <p/>
075 * this.{@link #appliedPaths appliedPaths}.put(path, values);
076 * </code></pre>
077 *
078 * @param path the application context path to match for executing this filter.
079 * @param config the specified for <em>this particular filter only</em> for the given <code>path</code>
080 * @return this configured filter.
081 */
082 public Filter processPathConfig(String path, String config) {
083 String[] values = null;
084 if (config != null) {
085 values = split(config);
086 }
087
088 this.appliedPaths.put(path, values);
089 return this;
090 }
091
092 /**
093 * Returns the context path within the application based on the specified <code>request</code>.
094 * <p/>
095 * This implementation merely delegates to
096 * {@link WebUtils#getPathWithinApplication(javax.servlet.http.HttpServletRequest) WebUtils.getPathWithinApplication(request)},
097 * but can be overridden by subclasses for custom logic.
098 *
099 * @param request the incoming <code>ServletRequest</code>
100 * @return the context path within the application.
101 */
102 protected String getPathWithinApplication(ServletRequest request) {
103 return WebUtils.getPathWithinApplication(WebUtils.toHttp(request));
104 }
105
106 /**
107 * Returns <code>true</code> if the incoming <code>request</code> matches the specified <code>path</code> pattern,
108 * <code>false</code> otherwise.
109 * <p/>
110 * The default implementation acquires the <code>request</code>'s path within the application and determines
111 * if that matches:
112 * <p/>
113 * <code>String requestURI = {@link #getPathWithinApplication(javax.servlet.ServletRequest) getPathWithinApplication(request)};<br/>
114 * return {@link #pathsMatch(String, String) pathsMatch(path,requestURI)}</code>
115 *
116 * @param path the configured url pattern to check the incoming request against.
117 * @param request the incoming ServletRequest
118 * @return <code>true</code> if the incoming <code>request</code> matches the specified <code>path</code> pattern,
119 * <code>false</code> otherwise.
120 */
121 protected boolean pathsMatch(String path, ServletRequest request) {
122 String requestURI = getPathWithinApplication(request);
123 log.trace("Attempting to match pattern '{}' with current requestURI '{}'...", path, requestURI);
124 return pathsMatch(path, requestURI);
125 }
126
127 /**
128 * Returns <code>true</code> if the <code>path</code> matches the specified <code>pattern</code> string,
129 * <code>false</code> otherwise.
130 * <p/>
131 * Simply delegates to
132 * <b><code>this.pathMatcher.{@link PatternMatcher#matches(String, String) matches(pattern,path)}</code></b>,
133 * but can be overridden by subclasses for custom matching behavior.
134 *
135 * @param pattern the pattern to match against
136 * @param path the value to match with the specified <code>pattern</code>
137 * @return <code>true</code> if the <code>path</code> matches the specified <code>pattern</code> string,
138 * <code>false</code> otherwise.
139 */
140 protected boolean pathsMatch(String pattern, String path) {
141 return pathMatcher.matches(pattern, path);
142 }
143
144 /**
145 * Implementation that handles path-matching behavior before a request is evaluated. If the path matches and
146 * the filter
147 * {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse, String, Object) isEnabled} for
148 * that path/config, the request will be allowed through via the result from
149 * {@link #onPreHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) onPreHandle}. If the
150 * path does not match or the filter is not enabled for that path, this filter will allow passthrough immediately
151 * to allow the {@code FilterChain} to continue executing.
152 * <p/>
153 * In order to retain path-matching functionality, subclasses should not override this method if at all
154 * possible, and instead override
155 * {@link #onPreHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) onPreHandle} instead.
156 *
157 * @param request the incoming ServletRequest
158 * @param response the outgoing ServletResponse
159 * @return {@code true} if the filter chain is allowed to continue to execute, {@code false} if a subclass has
160 * handled the request explicitly.
161 * @throws Exception if an error occurs
162 */
163 protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
164
165 if (this.appliedPaths == null || this.appliedPaths.isEmpty()) {
166 if (log.isTraceEnabled()) {
167 log.trace("appliedPaths property is null or empty. This Filter will passthrough immediately.");
168 }
169 return true;
170 }
171
172 for (String path : this.appliedPaths.keySet()) {
173 // If the path does match, then pass on to the subclass implementation for specific checks
174 //(first match 'wins'):
175 if (pathsMatch(path, request)) {
176 log.trace("Current requestURI matches pattern '{}'. Determining filter chain execution...", path);
177 Object config = this.appliedPaths.get(path);
178 return isFilterChainContinued(request, response, path, config);
179 }
180 }
181
182 //no path matched, allow the request to go through:
183 return true;
184 }
185
186 /**
187 * Simple method to abstract out logic from the preHandle implementation - it was getting a bit unruly.
188 *
189 * @since 1.2
190 */
191 @SuppressWarnings({"JavaDoc"})
192 private boolean isFilterChainContinued(ServletRequest request, ServletResponse response,
193 String path, Object pathConfig) throws Exception {
194
195 if (isEnabled(request, response, path, pathConfig)) { //isEnabled check added in 1.2
196 if (log.isTraceEnabled()) {
197 log.trace("Filter '{}' is enabled for the current request under path '{}' with config [{}]. " +
198 "Delegating to subclass implementation for 'onPreHandle' check.",
199 new Object[]{getName(), path, pathConfig});
200 }
201 //The filter is enabled for this specific request, so delegate to subclass implementations
202 //so they can decide if the request should continue through the chain or not:
203 return onPreHandle(request, response, pathConfig);
204 }
205
206 if (log.isTraceEnabled()) {
207 log.trace("Filter '{}' is disabled for the current request under path '{}' with config [{}]. " +
208 "The next element in the FilterChain will be called immediately.",
209 new Object[]{getName(), path, pathConfig});
210 }
211 //This filter is disabled for this specific request,
212 //return 'true' immediately to indicate that the filter will not process the request
213 //and let the request/response to continue through the filter chain:
214 return true;
215 }
216
217 /**
218 * This default implementation always returns {@code true} and should be overridden by subclasses for custom
219 * logic if necessary.
220 *
221 * @param request the incoming ServletRequest
222 * @param response the outgoing ServletResponse
223 * @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings.
224 * @return {@code true} if the request should be able to continue, {@code false} if the filter will
225 * handle the response directly.
226 * @throws Exception if an error occurs
227 * @see #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse, String, Object)
228 */
229 protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
230 return true;
231 }
232
233 /**
234 * Path-matching version of the parent class's
235 * {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} method, but additionally allows
236 * for inspection of any path-specific configuration values corresponding to the specified request. Subclasses
237 * may wish to inspect this additional mapped configuration to determine if the filter is enabled or not.
238 * <p/>
239 * This method's default implementation ignores the {@code path} and {@code mappedValue} arguments and merely
240 * returns the value from a call to {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse)}.
241 * It is expected that subclasses override this method if they need to perform enable/disable logic for a specific
242 * request based on any path-specific config for the filter instance.
243 *
244 * @param request the incoming servlet request
245 * @param response the outbound servlet response
246 * @param path the path matched for the incoming servlet request that has been configured with the given {@code mappedValue}.
247 * @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings for the given {@code path}.
248 * @return {@code true} if this filter should filter the specified request, {@code false} if it should let the
249 * request/response pass through immediately to the next element in the {@code FilterChain}.
250 * @throws Exception in the case of any error
251 * @since 1.2
252 */
253 @SuppressWarnings({"UnusedParameters"})
254 protected boolean isEnabled(ServletRequest request, ServletResponse response, String path, Object mappedValue)
255 throws Exception {
256 return isEnabled(request, response);
257 }
258 }