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