1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 package org.apache.shiro.web.filter; 20 21 import org.apache.shiro.util.AntPathMatcher; 22 import org.apache.shiro.util.PatternMatcher; 23 import org.apache.shiro.util.StringUtils; 24 import org.apache.shiro.web.servlet.AdviceFilter; 25 import org.apache.shiro.web.util.WebUtils; 26 import org.owasp.encoder.Encode; 27 import org.slf4j.Logger; 28 import org.slf4j.LoggerFactory; 29 30 import javax.servlet.Filter; 31 import javax.servlet.ServletRequest; 32 import javax.servlet.ServletResponse; 33 import java.util.LinkedHashMap; 34 import java.util.Map; 35 36 import static org.apache.shiro.util.StringUtils.split; 37 38 /** 39 * <p>Base class for Filters that will process only specified paths and allow all others to pass through.</p> 40 * 41 * @since 0.9 42 */ 43 public abstract class PathMatchingFilter extends AdviceFilter implements PathConfigProcessor { 44 45 /** 46 * Log available to this class only 47 */ 48 private static final Logger log = LoggerFactory.getLogger(PathMatchingFilter.class); 49 50 private static final String DEFAULT_PATH_SEPARATOR = "/"; 51 52 /** 53 * PatternMatcher used in determining which paths to react to for a given request. 54 */ 55 protected PatternMatcher pathMatcher = new AntPathMatcher(); 56 57 /** 58 * A collection of path-to-config entries where the key is a path which this filter should process and 59 * the value is the (possibly null) configuration element specific to this Filter for that specific path. 60 * <p/> 61 * <p>To put it another way, the keys are the paths (urls) that this Filter will process. 62 * <p>The values are filter-specific data that this Filter should use when processing the corresponding 63 * key (path). The values can be null if no Filter-specific config was specified for that url. 64 */ 65 protected Map<String, Object> appliedPaths = new LinkedHashMap<String, Object>(); 66 67 /** 68 * Splits any comma-delmited values that might be found in the <code>config</code> argument and sets the resulting 69 * <code>String[]</code> array on the <code>appliedPaths</code> internal Map. 70 * <p/> 71 * That is: 72 * <pre><code> 73 * String[] values = null; 74 * if (config != null) { 75 * values = split(config); 76 * } 77 * <p/> 78 * this.{@link #appliedPaths appliedPaths}.put(path, values); 79 * </code></pre> 80 * 81 * @param path the application context path to match for executing this filter. 82 * @param config the specified for <em>this particular filter only</em> for the given <code>path</code> 83 * @return this configured filter. 84 */ 85 public Filter processPathConfig(String path, String config) { 86 String[] values = null; 87 if (config != null) { 88 values = split(config); 89 } 90 91 this.appliedPaths.put(path, values); 92 return this; 93 } 94 95 /** 96 * Returns the context path within the application based on the specified <code>request</code>. 97 * <p/> 98 * This implementation merely delegates to 99 * {@link WebUtils#getPathWithinApplication(javax.servlet.http.HttpServletRequest) WebUtils.getPathWithinApplication(request)}, 100 * but can be overridden by subclasses for custom logic. 101 * 102 * @param request the incoming <code>ServletRequest</code> 103 * @return the context path within the application. 104 */ 105 protected String getPathWithinApplication(ServletRequest request) { 106 return WebUtils.getPathWithinApplication(WebUtils.toHttp(request)); 107 } 108 109 /** 110 * Returns <code>true</code> if the incoming <code>request</code> matches the specified <code>path</code> pattern, 111 * <code>false</code> otherwise. 112 * <p/> 113 * The default implementation acquires the <code>request</code>'s path within the application and determines 114 * if that matches: 115 * <p/> 116 * <code>String requestURI = {@link #getPathWithinApplication(javax.servlet.ServletRequest) getPathWithinApplication(request)};<br/> 117 * return {@link #pathsMatch(String, String) pathsMatch(path,requestURI)}</code> 118 * 119 * @param path the configured url pattern to check the incoming request against. 120 * @param request the incoming ServletRequest 121 * @return <code>true</code> if the incoming <code>request</code> matches the specified <code>path</code> pattern, 122 * <code>false</code> otherwise. 123 */ 124 protected boolean pathsMatch(String path, ServletRequest request) { 125 String requestURI = getPathWithinApplication(request); 126 if (requestURI != null && !DEFAULT_PATH_SEPARATOR.equals(requestURI) 127 && requestURI.endsWith(DEFAULT_PATH_SEPARATOR)) { 128 requestURI = requestURI.substring(0, requestURI.length() - 1); 129 } 130 if (path != null && !DEFAULT_PATH_SEPARATOR.equals(path) 131 && path.endsWith(DEFAULT_PATH_SEPARATOR)) { 132 path = path.substring(0, path.length() - 1); 133 } 134 log.trace("Attempting to match pattern '{}' with current requestURI '{}'...", path, Encode.forHtml(requestURI)); 135 return pathsMatch(path, requestURI); 136 } 137 138 /** 139 * Returns <code>true</code> if the <code>path</code> matches the specified <code>pattern</code> string, 140 * <code>false</code> otherwise. 141 * <p/> 142 * Simply delegates to 143 * <b><code>this.pathMatcher.{@link PatternMatcher#matches(String, String) matches(pattern,path)}</code></b>, 144 * but can be overridden by subclasses for custom matching behavior. 145 * 146 * @param pattern the pattern to match against 147 * @param path the value to match with the specified <code>pattern</code> 148 * @return <code>true</code> if the <code>path</code> matches the specified <code>pattern</code> string, 149 * <code>false</code> otherwise. 150 */ 151 protected boolean pathsMatch(String pattern, String path) { 152 return pathMatcher.matches(pattern, path); 153 } 154 155 /** 156 * Implementation that handles path-matching behavior before a request is evaluated. If the path matches and 157 * the filter 158 * {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse, String, Object) isEnabled} for 159 * that path/config, the request will be allowed through via the result from 160 * {@link #onPreHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) onPreHandle}. If the 161 * path does not match or the filter is not enabled for that path, this filter will allow passthrough immediately 162 * to allow the {@code FilterChain} to continue executing. 163 * <p/> 164 * In order to retain path-matching functionality, subclasses should not override this method if at all 165 * possible, and instead override 166 * {@link #onPreHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) onPreHandle} instead. 167 * 168 * @param request the incoming ServletRequest 169 * @param response the outgoing ServletResponse 170 * @return {@code true} if the filter chain is allowed to continue to execute, {@code false} if a subclass has 171 * handled the request explicitly. 172 * @throws Exception if an error occurs 173 */ 174 protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { 175 176 if (this.appliedPaths == null || this.appliedPaths.isEmpty()) { 177 if (log.isTraceEnabled()) { 178 log.trace("appliedPaths property is null or empty. This Filter will passthrough immediately."); 179 } 180 return true; 181 } 182 183 for (String path : this.appliedPaths.keySet()) { 184 // If the path does match, then pass on to the subclass implementation for specific checks 185 //(first match 'wins'): 186 if (pathsMatch(path, request)) { 187 log.trace("Current requestURI matches pattern '{}'. Determining filter chain execution...", path); 188 Object config = this.appliedPaths.get(path); 189 return isFilterChainContinued(request, response, path, config); 190 } 191 } 192 193 //no path matched, allow the request to go through: 194 return true; 195 } 196 197 /** 198 * Simple method to abstract out logic from the preHandle implementation - it was getting a bit unruly. 199 * 200 * @since 1.2 201 */ 202 @SuppressWarnings({"JavaDoc"}) 203 private boolean isFilterChainContinued(ServletRequest request, ServletResponse response, 204 String path, Object pathConfig) throws Exception { 205 206 if (isEnabled(request, response, path, pathConfig)) { //isEnabled check added in 1.2 207 if (log.isTraceEnabled()) { 208 log.trace("Filter '{}' is enabled for the current request under path '{}' with config [{}]. " + 209 "Delegating to subclass implementation for 'onPreHandle' check.", 210 new Object[]{getName(), path, pathConfig}); 211 } 212 //The filter is enabled for this specific request, so delegate to subclass implementations 213 //so they can decide if the request should continue through the chain or not: 214 return onPreHandle(request, response, pathConfig); 215 } 216 217 if (log.isTraceEnabled()) { 218 log.trace("Filter '{}' is disabled for the current request under path '{}' with config [{}]. " + 219 "The next element in the FilterChain will be called immediately.", 220 new Object[]{getName(), path, pathConfig}); 221 } 222 //This filter is disabled for this specific request, 223 //return 'true' immediately to indicate that the filter will not process the request 224 //and let the request/response to continue through the filter chain: 225 return true; 226 } 227 228 /** 229 * This default implementation always returns {@code true} and should be overridden by subclasses for custom 230 * logic if necessary. 231 * 232 * @param request the incoming ServletRequest 233 * @param response the outgoing ServletResponse 234 * @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings. 235 * @return {@code true} if the request should be able to continue, {@code false} if the filter will 236 * handle the response directly. 237 * @throws Exception if an error occurs 238 * @see #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse, String, Object) 239 */ 240 protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { 241 return true; 242 } 243 244 /** 245 * Path-matching version of the parent class's 246 * {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} method, but additionally allows 247 * for inspection of any path-specific configuration values corresponding to the specified request. Subclasses 248 * may wish to inspect this additional mapped configuration to determine if the filter is enabled or not. 249 * <p/> 250 * This method's default implementation ignores the {@code path} and {@code mappedValue} arguments and merely 251 * returns the value from a call to {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse)}. 252 * It is expected that subclasses override this method if they need to perform enable/disable logic for a specific 253 * request based on any path-specific config for the filter instance. 254 * 255 * @param request the incoming servlet request 256 * @param response the outbound servlet response 257 * @param path the path matched for the incoming servlet request that has been configured with the given {@code mappedValue}. 258 * @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings for the given {@code path}. 259 * @return {@code true} if this filter should filter the specified request, {@code false} if it should let the 260 * request/response pass through immediately to the next element in the {@code FilterChain}. 261 * @throws Exception in the case of any error 262 * @since 1.2 263 */ 264 @SuppressWarnings({"UnusedParameters"}) 265 protected boolean isEnabled(ServletRequest request, ServletResponse response, String path, Object mappedValue) 266 throws Exception { 267 return isEnabled(request, response); 268 } 269 }