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.web.servlet.AdviceFilter;
024 import org.apache.shiro.web.util.WebUtils;
025 import org.slf4j.Logger;
026 import org.slf4j.LoggerFactory;
027
028 import javax.servlet.Filter;
029 import javax.servlet.ServletRequest;
030 import javax.servlet.ServletResponse;
031 import java.util.LinkedHashMap;
032 import java.util.Map;
033
034 import static org.apache.shiro.util.StringUtils.split;
035
036 /**
037 * <p>Base class for Filters that will process only specified paths and allow all others to pass through.</p>
038 *
039 * @since 0.9
040 */
041 public abstract class PathMatchingFilter extends AdviceFilter implements PathConfigProcessor {
042
043 /**
044 * Log available to this class only
045 */
046 private static final Logger log = LoggerFactory.getLogger(PathMatchingFilter.class);
047
048 /**
049 * PatternMatcher used in determining which paths to react to for a given request.
050 */
051 protected PatternMatcher pathMatcher = new AntPathMatcher();
052
053 /**
054 * A collection of path-to-config entries where the key is a path which this filter should process and
055 * the value is the (possibly null) configuration element specific to this Filter for that specific path.
056 *
057 * <p>To put it another way, the keys are the paths (urls) that this Filter will process.
058 * <p>The values are filter-specific data that this Filter should use when processing the corresponding
059 * key (path). The values can be null if no Filter-specific config was specified for that url.
060 */
061 protected Map<String, Object> appliedPaths = new LinkedHashMap<String, Object>();
062
063 /**
064 * Splits any comma-delmited values that might be found in the <code>config</code> argument and sets the resulting
065 * <code>String[]</code> array on the <code>appliedPaths</code> internal Map.
066 * <p/>
067 * That is:
068 * <pre><code>
069 * String[] values = null;
070 * if (config != null) {
071 * values = split(config);
072 * }
073 *
074 * this.{@link #appliedPaths appliedPaths}.put(path, values);
075 * </code></pre>
076 *
077 * @param path the application context path to match for executing this filter.
078 * @param config the specified for <em>this particular filter only</em> for the given <code>path</code>
079 * @return this configured filter.
080 */
081 public Filter processPathConfig(String path, String config) {
082 String[] values = null;
083 if (config != null) {
084 values = split(config);
085 }
086
087 this.appliedPaths.put(path, values);
088 return this;
089 }
090
091 /**
092 * Returns the context path within the application based on the specified <code>request</code>.
093 * <p/>
094 * This implementation merely delegates to
095 * {@link WebUtils#getPathWithinApplication(javax.servlet.http.HttpServletRequest) WebUtils.getPathWithinApplication(request)},
096 * but can be overridden by subclasses for custom logic.
097 *
098 * @param request the incoming <code>ServletRequest</code>
099 * @return the context path within the application.
100 */
101 protected String getPathWithinApplication(ServletRequest request) {
102 return WebUtils.getPathWithinApplication(WebUtils.toHttp(request));
103 }
104
105 /**
106 * Returns <code>true</code> if the incoming <code>request</code> matches the specified <code>path</code> pattern,
107 * <code>false</code> otherwise.
108 * <p/>
109 * The default implementation acquires the <code>request</code>'s path within the application and determines
110 * if that matches:
111 * <p/>
112 * <code>String requestURI = {@link #getPathWithinApplication(javax.servlet.ServletRequest) getPathWithinApplication(request)};<br/>
113 * return {@link #pathsMatch(String, String) pathsMatch(path,requestURI)}</code>
114 *
115 * @param path the configured url pattern to check the incoming request against.
116 * @param request the incoming ServletRequest
117 * @return <code>true</code> if the incoming <code>request</code> matches the specified <code>path</code> pattern,
118 * <code>false</code> otherwise.
119 */
120 protected boolean pathsMatch(String path, ServletRequest request) {
121 String requestURI = getPathWithinApplication(request);
122 if (log.isTraceEnabled()) {
123 log.trace("Attempting to match pattern [" + path + "] with current requestURI [" + requestURI + "]...");
124 }
125 return pathsMatch(path, requestURI);
126 }
127
128 /**
129 * Returns <code>true</code> if the <code>path</code> matches the specified <code>pattern</code> string,
130 * <code>false</code> otherwise.
131 * <p/>
132 * Simply delegates to
133 * <b><code>this.pathMatcher.{@link PatternMatcher#matches(String, String) matches(pattern,path)}</code></b>,
134 * but can be overridden by subclasses for custom matching behavior.
135 *
136 * @param pattern the pattern to match against
137 * @param path the value to match with the specified <code>pattern</code>
138 * @return <code>true</code> if the <code>path</code> matches the specified <code>pattern</code> string,
139 * <code>false</code> otherwise.
140 */
141 protected boolean pathsMatch(String pattern, String path) {
142 return pathMatcher.matches(pattern, path);
143 }
144
145 /**
146 * Implementation that handles path-matching behavior before a request is evaluated. If the path matches,
147 * the request will be allowed through via the result from
148 * {@link #onPreHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) onPreHandle}. If the
149 * path does not match, this filter will allow passthrough immediately.
150 *
151 * <p>In order to retain path-matching functionality, subclasses should not override this method if at all
152 * possible, and instead override
153 * {@link #onPreHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) onPreHandle} instead.
154 *
155 * @param request the incoming ServletRequest
156 * @param response the outgoing ServletResponse
157 * @return true - allow the request chain to continue in this default implementation
158 * @throws Exception
159 */
160 public boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
161
162 if (this.appliedPaths == null || this.appliedPaths.isEmpty()) {
163 if (log.isTraceEnabled()) {
164 log.trace("appliedPaths property is null or empty. This Filter will passthrough immediately.");
165 }
166 return true;
167 }
168
169 for (String path : this.appliedPaths.keySet()) {
170 // If the path does match, then pass on to the subclass implementation for specific checks
171 //(first match 'wins'):
172 if (pathsMatch(path, request)) {
173 if (log.isTraceEnabled()) {
174 log.trace("Current requestURI matches pattern [" + path + "]. Performing onPreHandle check...");
175 }
176 Object config = this.appliedPaths.get(path);
177 return onPreHandle(request, response, config);
178 }
179 }
180
181 //no path matched, allow the request to go through:
182 return true;
183 }
184
185 /**
186 * Default implementation always returns <code>true</code>. Should be overridden by subclasses for custom
187 * logic.
188 *
189 * @param request the incoming ServletRequest
190 * @param response the outgoing ServletResponse
191 * @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings.
192 * @return <code>true</code> if the request should be able to continue, <code>false</code> if the filter will
193 * handle the response directly.
194 * @throws Exception if an error occurs
195 */
196 protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
197 return true;
198 }
199 }