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 pre-pending 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 }