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.servlet;
20
21 import org.apache.shiro.SecurityUtils;
22 import org.apache.shiro.session.Session;
23 import org.apache.shiro.subject.ExecutionException;
24 import org.apache.shiro.subject.Subject;
25 import org.apache.shiro.web.config.ShiroFilterConfiguration;
26 import org.apache.shiro.web.filter.mgt.FilterChainResolver;
27 import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
28 import org.apache.shiro.web.mgt.WebSecurityManager;
29 import org.apache.shiro.web.subject.WebSubject;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 import javax.servlet.FilterChain;
34 import javax.servlet.ServletException;
35 import javax.servlet.ServletRequest;
36 import javax.servlet.ServletResponse;
37 import javax.servlet.http.HttpServletRequest;
38 import javax.servlet.http.HttpServletResponse;
39 import java.io.IOException;
40 import java.util.concurrent.Callable;
41
42 /**
43 * Abstract base class that provides all standard Shiro request filtering behavior and expects
44 * subclasses to implement configuration-specific logic (INI, XML, .properties, etc.).
45 * <p/>
46 * Subclasses should perform configuration and construction logic in an overridden
47 * {@link #init()} method implementation. That implementation should make available any constructed
48 * {@code SecurityManager} and {@code FilterChainResolver} by calling
49 * {@link #setSecurityManager(org.apache.shiro.web.mgt.WebSecurityManager)} and
50 * {@link #setFilterChainResolver(org.apache.shiro.web.filter.mgt.FilterChainResolver)} methods respectively.
51 * <h3>Static SecurityManager</h3>
52 * By default, the {@code SecurityManager} instance enabled by this filter <em>will not</em> be enabled in static
53 * memory via the {@code SecurityUtils.}
54 * {@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager}
55 * method. Instead, it is expected that Subject instances will always be constructed on a request-processing thread
56 * via instances of this Filter class.
57 * <p/>
58 * However, if you need to construct {@code Subject} instances on separate (non request-processing) threads, it might
59 * be easiest to enable the SecurityManager to be available in static memory via the
60 * {@link SecurityUtils#getSecurityManager()} method. You can do this by additionally specifying an {@code init-param}:
61 * <pre>
62 * <filter>
63 * ... other config here ...
64 * <init-param>
65 * <param-name>staticSecurityManagerEnabled</param-name>
66 * <param-value>true</param-value>
67 * </init-param>
68 * </filter>
69 * </pre>
70 * See the Shiro <a href="http://shiro.apache.org/subject.html">Subject documentation</a> for more information as to
71 * if you would do this, particularly the sections on the {@code Subject.Builder} and Thread Association.
72 *
73 * @see <a href="http://shiro.apache.org/subject.html">Subject documentation</a>
74 * @since 1.0
75 */
76 public abstract class AbstractShiroFilter extends OncePerRequestFilter {
77
78 private static final Logger LOGGER = LoggerFactory.getLogger(AbstractShiroFilter.class);
79
80 private static final String STATIC_INIT_PARAM_NAME = "staticSecurityManagerEnabled";
81
82 // Reference to the security manager used by this filter
83 private WebSecurityManager securityManager;
84
85 // Used to determine which chain should handle an incoming request/response
86 private FilterChainResolver filterChainResolver;
87
88 /**
89 * Whether to bind the constructed SecurityManager instance to static memory (via
90 * SecurityUtils.setSecurityManager).
91 * This was added to support <a href="https://issues.apache.org/jira/browse/SHIRO-287"/>
92 *
93 * @since 1.2
94 */
95 private boolean staticSecurityManagerEnabled;
96
97 protected AbstractShiroFilter() {
98 this.staticSecurityManagerEnabled = false;
99 }
100
101 public WebSecurityManager getSecurityManager() {
102 return securityManager;
103 }
104
105 public void setSecurityManager(WebSecurityManager sm) {
106 this.securityManager = sm;
107 }
108
109 public FilterChainResolver getFilterChainResolver() {
110 return filterChainResolver;
111 }
112
113 public void setFilterChainResolver(FilterChainResolver filterChainResolver) {
114 this.filterChainResolver = filterChainResolver;
115 }
116
117 public void setShiroFilterConfiguration(ShiroFilterConfiguration config) {
118 this.setFilterOncePerRequest(config.isFilterOncePerRequest());
119
120 // this property could have already been set with a servlet config param
121 this.setStaticSecurityManagerEnabled(config.isStaticSecurityManagerEnabled() || isStaticSecurityManagerEnabled());
122 }
123
124 /**
125 * Returns {@code true} if the constructed {@link #getSecurityManager() securityManager} reference should be bound
126 * to static memory (via
127 * {@code SecurityUtils.}{@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager}),
128 * {@code false} otherwise.
129 * <p/>
130 * The default value is {@code false}.
131 * <p/>
132 *
133 * @return {@code true} if the constructed {@link #getSecurityManager() securityManager} reference should be bound
134 * to static memory (via {@code SecurityUtils.}{@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager)
135 * setSecurityManager}),
136 * {@code false} otherwise.
137 * @see <a href="https://issues.apache.org/jira/browse/SHIRO-287">SHIRO-287</a>
138 * @since 1.2
139 */
140 public boolean isStaticSecurityManagerEnabled() {
141 return staticSecurityManagerEnabled;
142 }
143
144 /**
145 * Sets if the constructed {@link #getSecurityManager() securityManager} reference should be bound
146 * to static memory (via {@code SecurityUtils.}
147 * {@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager}).
148 * <p/>
149 * The default value is {@code false}.
150 *
151 * @param staticSecurityManagerEnabled if the constructed {@link #getSecurityManager() securityManager} reference
152 * should be bound to static memory (via
153 * {@code SecurityUtils.}
154 * {@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager)
155 * setSecurityManager}).
156 * @see <a href="https://issues.apache.org/jira/browse/SHIRO-287">SHIRO-287</a>
157 * @since 1.2
158 */
159 public void setStaticSecurityManagerEnabled(boolean staticSecurityManagerEnabled) {
160 this.staticSecurityManagerEnabled = staticSecurityManagerEnabled;
161 }
162
163 protected final void onFilterConfigSet() throws Exception {
164 //added in 1.2 for SHIRO-287:
165 applyStaticSecurityManagerEnabledConfig();
166 init();
167 ensureSecurityManager();
168 //added in 1.2 for SHIRO-287:
169 if (isStaticSecurityManagerEnabled()) {
170 SecurityUtils.setSecurityManager(getSecurityManager());
171 }
172 }
173
174 /**
175 * Checks if the init-param that configures the filter to use static memory has been configured, and if so,
176 * sets the {@link #setStaticSecurityManagerEnabled(boolean)} attribute with the configured value.
177 *
178 * @see <a href="https://issues.apache.org/jira/browse/SHIRO-287">SHIRO-287</a>
179 * @since 1.2
180 */
181 private void applyStaticSecurityManagerEnabledConfig() {
182 String value = getInitParam(STATIC_INIT_PARAM_NAME);
183 if (value != null) {
184 boolean b = Boolean.parseBoolean(value);
185 if (b) {
186 setStaticSecurityManagerEnabled(b);
187 }
188 }
189 }
190
191 public void init() throws Exception {
192 }
193
194 /**
195 * A fallback mechanism called in {@link #onFilterConfigSet()} to ensure that the
196 * {@link #getSecurityManager() securityManager} property has been set by configuration, and if not,
197 * creates one automatically.
198 */
199 private void ensureSecurityManager() {
200 WebSecurityManager securityManager = getSecurityManager();
201 if (securityManager == null) {
202 LOGGER.info("No SecurityManager configured. Creating default.");
203 securityManager = createDefaultSecurityManager();
204 setSecurityManager(securityManager);
205 }
206 }
207
208 protected WebSecurityManager createDefaultSecurityManager() {
209 return new DefaultWebSecurityManager();
210 }
211
212 protected boolean isHttpSessions() {
213 return getSecurityManager().isHttpSessionMode();
214 }
215
216 /**
217 * Wraps the original HttpServletRequest in a {@link ShiroHttpServletRequest}, which is required for supporting
218 * Servlet Specification behavior backed by a {@link org.apache.shiro.subject.Subject Subject} instance.
219 *
220 * @param orig the original Servlet Container-provided incoming {@code HttpServletRequest} instance.
221 * @return {@link ShiroHttpServletRequest ShiroHttpServletRequest} instance wrapping the original.
222 * @since 1.0
223 */
224 protected ServletRequest wrapServletRequest(HttpServletRequest orig) {
225 return new ShiroHttpServletRequest(orig, getServletContext(), isHttpSessions());
226 }
227
228 /**
229 * Prepares the {@code ServletRequest} instance that will be passed to the {@code FilterChain} for request
230 * processing.
231 * <p/>
232 * If the {@code ServletRequest} is an instance of {@link HttpServletRequest}, the value returned from this method
233 * is obtained by calling {@link #wrapServletRequest(javax.servlet.http.HttpServletRequest)} to allow Shiro-specific
234 * HTTP behavior, otherwise the original {@code ServletRequest} argument is returned.
235 *
236 * @param request the incoming ServletRequest
237 * @param response the outgoing ServletResponse
238 * @param chain the Servlet Container provided {@code FilterChain} that will receive the returned request.
239 * @return the {@code ServletRequest} instance that will be passed to the {@code FilterChain} for request processing.
240 * @since 1.0
241 */
242 @SuppressWarnings({"UnusedDeclaration"})
243 protected ServletRequest prepareServletRequest(ServletRequest request, ServletResponse response, FilterChain chain) {
244 ServletRequest toUse = request;
245 if (request instanceof HttpServletRequest) {
246 HttpServletRequest http = (HttpServletRequest) request;
247 toUse = wrapServletRequest(http);
248 }
249 return toUse;
250 }
251
252 /**
253 * Returns a new {@link ShiroHttpServletResponse} instance, wrapping the {@code orig} argument, in order to provide
254 * correct URL rewriting behavior required by the Servlet Specification when using Shiro-based sessions (and not
255 * Servlet Container HTTP-based sessions).
256 *
257 * @param orig the original {@code HttpServletResponse} instance provided by the Servlet Container.
258 * @param request the {@code ShiroHttpServletRequest} instance wrapping the original request.
259 * @return the wrapped ServletResponse instance to use during {@link FilterChain} execution.
260 * @since 1.0
261 */
262 protected ServletResponse wrapServletResponse(HttpServletResponse orig, ShiroHttpServletRequest request) {
263 return new ShiroHttpServletResponse(orig, getServletContext(), request);
264 }
265
266 /**
267 * Prepares the {@code ServletResponse} instance that will be passed to the {@code FilterChain} for request
268 * processing.
269 * <p/>
270 * This implementation delegates to {@link #wrapServletRequest(javax.servlet.http.HttpServletRequest)}
271 * only if Shiro-based sessions are enabled (that is, !{@link #isHttpSessions()}) and the request instance is a
272 * {@link ShiroHttpServletRequest}. This ensures that any URL rewriting that occurs is handled correctly using the
273 * Shiro-managed Session's sessionId and not a servlet container session ID.
274 * <p/>
275 * If HTTP-based sessions are enabled (the default), then this method does nothing and just returns the
276 * {@code ServletResponse} argument as-is, relying on the default Servlet Container URL rewriting logic.
277 *
278 * @param request the incoming ServletRequest
279 * @param response the outgoing ServletResponse
280 * @param chain the Servlet Container provided {@code FilterChain} that will receive the returned request.
281 * @return the {@code ServletResponse} instance that will be passed to the {@code FilterChain} during request processing.
282 * @since 1.0
283 */
284 @SuppressWarnings({"UnusedDeclaration"})
285 protected ServletResponse prepareServletResponse(ServletRequest request, ServletResponse response, FilterChain chain) {
286 ServletResponse toUse = response;
287 if (!isHttpSessions() && (request instanceof ShiroHttpServletRequest)
288 && (response instanceof HttpServletResponse)) {
289 //the ShiroHttpServletResponse exists to support URL rewriting for session ids. This is only needed if
290 //using Shiro sessions (i.e. not simple HttpSession based sessions):
291 toUse = wrapServletResponse((HttpServletResponse) response, (ShiroHttpServletRequest) request);
292 }
293 return toUse;
294 }
295
296 /**
297 * Creates a {@link WebSubject} instance to associate with the incoming request/response pair which will be used
298 * throughout the request/response execution.
299 *
300 * @param request the incoming {@code ServletRequest}
301 * @param response the outgoing {@code ServletResponse}
302 * @return the {@code WebSubject} instance to associate with the request/response execution
303 * @since 1.0
304 */
305 protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
306 return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
307 }
308
309 /**
310 * Updates any 'native' Session's last access time that might exist to the timestamp when this method is called.
311 * If native sessions are not enabled (that is, standard Servlet container sessions are being used) or there is no
312 * session ({@code subject.getSession(false) == null}), this method does nothing.
313 * <p/>This method implementation merely calls
314 * <code>Session.{@link org.apache.shiro.session.Session#touch() touch}()</code> on the session.
315 *
316 * @param request incoming request - ignored, but available to subclasses that might wish to override this method
317 * @param response outgoing response - ignored, but available to subclasses that might wish to override this method
318 * @since 1.0
319 */
320 @SuppressWarnings({"UnusedDeclaration"})
321 protected void updateSessionLastAccessTime(ServletRequest request, ServletResponse response) {
322 //'native' sessions
323 if (!isHttpSessions()) {
324 Subject subject = SecurityUtils.getSubject();
325 //Subject should never _ever_ be null, but just in case:
326 if (subject != null) {
327 Session session = subject.getSession(false);
328 if (session != null) {
329 try {
330 session.touch();
331 } catch (Throwable t) {
332 LOGGER.error("session.touch() method invocation has failed. Unable to update "
333 + "the corresponding session's last access time based on the incoming request.", t);
334 }
335 }
336 }
337 }
338 }
339
340 /**
341 * {@code doFilterInternal} implementation that sets-up, executes, and cleans-up a Shiro-filtered request. It
342 * performs the following ordered operations:
343 * <ol>
344 * <li>{@link #prepareServletRequest(ServletRequest, ServletResponse, FilterChain) Prepares}
345 * the incoming {@code ServletRequest} for use during Shiro's processing</li>
346 * <li>{@link #prepareServletResponse(ServletRequest, ServletResponse, FilterChain) Prepares}
347 * the outgoing {@code ServletResponse} for use during Shiro's processing</li>
348 * <li> {@link #createSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) Creates} a
349 * {@link Subject} instance based on the specified request/response pair.</li>
350 * <li>Finally {@link Subject#execute(Runnable) executes} the
351 * {@link #updateSessionLastAccessTime(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} and
352 * {@link #executeChain(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)}
353 * methods</li>
354 * </ol>
355 * <p/>
356 * The {@code Subject.}{@link Subject#execute(Runnable) execute(Runnable)} call in step #4 is used as an
357 * implementation technique to guarantee proper thread binding and restoration is completed successfully.
358 *
359 * @param servletRequest the incoming {@code ServletRequest}
360 * @param servletResponse the outgoing {@code ServletResponse}
361 * @param chain the container-provided {@code FilterChain} to execute
362 * @throws IOException if an IO error occurs
363 * @throws javax.servlet.ServletException if a Throwable other than an IOException
364 */
365 protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
366 throws ServletException, IOException {
367
368 Throwable t = null;
369
370 try {
371 final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
372 final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
373
374 final Subject subject = createSubject(request, response);
375
376 subject.execute((Callable<Void>) () -> {
377 updateSessionLastAccessTime(request, response);
378 executeChain(request, response, chain);
379 return null;
380 });
381 } catch (ExecutionException ex) {
382 t = ex.getCause();
383 } catch (Throwable throwable) {
384 t = throwable;
385 }
386
387 if (t != null) {
388 if (t instanceof ServletException) {
389 throw (ServletException) t;
390 }
391 if (t instanceof IOException) {
392 throw (IOException) t;
393 }
394 //otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one:
395 String msg = "Filtered request failed.";
396 throw new ServletException(msg, t);
397 }
398 }
399
400 /**
401 * Returns the {@code FilterChain} to execute for the given request.
402 * <p/>
403 * The {@code origChain} argument is the
404 * original {@code FilterChain} supplied by the Servlet Container, but it may be modified to provide
405 * more behavior by prepending further chains according to the Shiro configuration.
406 * <p/>
407 * This implementation returns the chain that will actually be executed by acquiring the chain from a
408 * {@link #getFilterChainResolver() filterChainResolver}. The resolver determines exactly which chain to
409 * execute, typically based on URL configuration. If no chain is returned from the resolver call
410 * (returns {@code null}), then the {@code origChain} will be returned by default.
411 *
412 * @param request the incoming ServletRequest
413 * @param response the outgoing ServletResponse
414 * @param origChain the original {@code FilterChain} provided by the Servlet Container
415 * @return the {@link FilterChain} to execute for the given request
416 * @since 1.0
417 */
418 protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
419
420 FilterChain chain = origChain;
421
422 FilterChainResolver resolver = getFilterChainResolver();
423 if (resolver == null) {
424 LOGGER.debug("No FilterChainResolver configured. Returning original FilterChain.");
425 return origChain;
426 }
427
428 FilterChain resolved = resolver.getChain(request, response, origChain);
429 if (resolved != null) {
430 LOGGER.trace("Resolved a configured FilterChain for the current request.");
431 chain = resolved;
432 } else {
433 LOGGER.trace("No FilterChain configured for the current request. Using the default.");
434 }
435
436 return chain;
437 }
438
439 /**
440 * Executes a {@link FilterChain} for the given request.
441 * <p/>
442 * This implementation first delegates to
443 * <code>{@link #getExecutionChain(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
444 * getExecutionChain}</code>
445 * to allow the application's Shiro configuration to determine exactly how the chain should execute. The resulting
446 * value from that call is then executed directly by calling the returned {@code FilterChain}'s
447 * {@link FilterChain#doFilter doFilter} method. That is:
448 * <pre>
449 * FilterChain chain = {@link #getExecutionChain}(request, response, origChain);
450 * chain.{@link FilterChain#doFilter doFilter}(request,response);</pre>
451 *
452 * @param request the incoming ServletRequest
453 * @param response the outgoing ServletResponse
454 * @param origChain the Servlet Container-provided chain that may be wrapped further by an application-configured
455 * chain of Filters.
456 * @throws IOException if the underlying {@code chain.doFilter} call results in an IOException
457 * @throws ServletException if the underlying {@code chain.doFilter} call results in a ServletException
458 * @since 1.0
459 */
460 protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
461 throws IOException, ServletException {
462 FilterChain chain = getExecutionChain(request, response, origChain);
463 chain.doFilter(request, response);
464 }
465 }