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 */ 019package org.apache.shiro.web.servlet; 020 021import org.apache.shiro.SecurityUtils; 022import org.apache.shiro.session.Session; 023import org.apache.shiro.subject.ExecutionException; 024import org.apache.shiro.subject.Subject; 025import org.apache.shiro.web.filter.mgt.FilterChainResolver; 026import org.apache.shiro.web.mgt.DefaultWebSecurityManager; 027import org.apache.shiro.web.mgt.WebSecurityManager; 028import org.apache.shiro.web.subject.WebSubject; 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031 032import javax.servlet.FilterChain; 033import javax.servlet.ServletException; 034import javax.servlet.ServletRequest; 035import javax.servlet.ServletResponse; 036import javax.servlet.http.HttpServletRequest; 037import javax.servlet.http.HttpServletResponse; 038import java.io.IOException; 039import java.util.concurrent.Callable; 040 041/** 042 * Abstract base class that provides all standard Shiro request filtering behavior and expects 043 * subclasses to implement configuration-specific logic (INI, XML, .properties, etc). 044 * <p/> 045 * Subclasses should perform configuration and construction logic in an overridden 046 * {@link #init()} method implementation. That implementation should make available any constructed 047 * {@code SecurityManager} and {@code FilterChainResolver} by calling 048 * {@link #setSecurityManager(org.apache.shiro.web.mgt.WebSecurityManager)} and 049 * {@link #setFilterChainResolver(org.apache.shiro.web.filter.mgt.FilterChainResolver)} methods respectively. 050 * <h3>Static SecurityManager</h3> 051 * By default the {@code SecurityManager} instance enabled by this filter <em>will not</em> be enabled in static 052 * memory via the {@code SecurityUtils.}{@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager} 053 * method. Instead, it is expected that Subject instances will always be constructed on a request-processing thread 054 * via instances of this Filter class. 055 * <p/> 056 * However, if you need to construct {@code Subject} instances on separate (non request-processing) threads, it might 057 * be easiest to enable the SecurityManager to be available in static memory via the 058 * {@link SecurityUtils#getSecurityManager()} method. You can do this by additionally specifying an {@code init-param}: 059 * <pre> 060 * <filter> 061 * ... other config here ... 062 * <init-param> 063 * <param-name>staticSecurityManagerEnabled</param-name> 064 * <param-value>true</param-value> 065 * </init-param> 066 * </filter> 067 * </pre> 068 * See the Shiro <a href="http://shiro.apache.org/subject.html">Subject documentation</a> for more information as to 069 * if you would do this, particularly the sections on the {@code Subject.Builder} and Thread Association. 070 * 071 * @since 1.0 072 * @see <a href="http://shiro.apache.org/subject.html">Subject documentation</a> 073 */ 074public abstract class AbstractShiroFilter extends OncePerRequestFilter { 075 076 private static final Logger log = LoggerFactory.getLogger(AbstractShiroFilter.class); 077 078 private static final String STATIC_INIT_PARAM_NAME = "staticSecurityManagerEnabled"; 079 080 // Reference to the security manager used by this filter 081 private WebSecurityManager securityManager; 082 083 // Used to determine which chain should handle an incoming request/response 084 private FilterChainResolver filterChainResolver; 085 086 /** 087 * Whether or not to bind the constructed SecurityManager instance to static memory (via 088 * SecurityUtils.setSecurityManager). This was added to support https://issues.apache.org/jira/browse/SHIRO-287 089 * @since 1.2 090 */ 091 private boolean staticSecurityManagerEnabled; 092 093 protected AbstractShiroFilter() { 094 this.staticSecurityManagerEnabled = false; 095 } 096 097 public WebSecurityManager getSecurityManager() { 098 return securityManager; 099 } 100 101 public void setSecurityManager(WebSecurityManager sm) { 102 this.securityManager = sm; 103 } 104 105 public FilterChainResolver getFilterChainResolver() { 106 return filterChainResolver; 107 } 108 109 public void setFilterChainResolver(FilterChainResolver filterChainResolver) { 110 this.filterChainResolver = filterChainResolver; 111 } 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 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 this.staticSecurityManagerEnabled = staticSecurityManagerEnabled; 147 } 148 149 protected final void onFilterConfigSet() throws Exception { 150 //added in 1.2 for SHIRO-287: 151 applyStaticSecurityManagerEnabledConfig(); 152 init(); 153 ensureSecurityManager(); 154 //added in 1.2 for SHIRO-287: 155 if (isStaticSecurityManagerEnabled()) { 156 SecurityUtils.setSecurityManager(getSecurityManager()); 157 } 158 } 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 String value = getInitParam(STATIC_INIT_PARAM_NAME); 169 if (value != null) { 170 Boolean b = Boolean.valueOf(value); 171 if (b != null) { 172 setStaticSecurityManagerEnabled(b); 173 } 174 } 175 } 176 177 public void init() throws Exception { 178 } 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 WebSecurityManager securityManager = getSecurityManager(); 187 if (securityManager == null) { 188 log.info("No SecurityManager configured. Creating default."); 189 securityManager = createDefaultSecurityManager(); 190 setSecurityManager(securityManager); 191 } 192 } 193 194 protected WebSecurityManager createDefaultSecurityManager() { 195 return new DefaultWebSecurityManager(); 196 } 197 198 protected boolean isHttpSessions() { 199 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 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 ServletRequest toUse = request; 231 if (request instanceof HttpServletRequest) { 232 HttpServletRequest http = (HttpServletRequest) request; 233 toUse = wrapServletRequest(http); 234 } 235 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 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 ServletResponse toUse = response; 273 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 toUse = wrapServletResponse((HttpServletResponse) response, (ShiroHttpServletRequest) request); 278 } 279 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 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 if (!isHttpSessions()) { //'native' sessions 309 Subject subject = SecurityUtils.getSubject(); 310 //Subject should never _ever_ be null, but just in case: 311 if (subject != null) { 312 Session session = subject.getSession(false); 313 if (session != null) { 314 try { 315 session.touch(); 316 } catch (Throwable t) { 317 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 } 320 } 321 } 322 } 323 } 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 Throwable t = null; 354 355 try { 356 final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain); 357 final ServletResponse response = prepareServletResponse(request, servletResponse, chain); 358 359 final Subject subject = createSubject(request, response); 360 361 //noinspection unchecked 362 subject.execute(new Callable() { 363 public Object call() throws Exception { 364 updateSessionLastAccessTime(request, response); 365 executeChain(request, response, chain); 366 return null; 367 } 368 }); 369 } catch (ExecutionException ex) { 370 t = ex.getCause(); 371 } catch (Throwable throwable) { 372 t = throwable; 373 } 374 375 if (t != null) { 376 if (t instanceof ServletException) { 377 throw (ServletException) t; 378 } 379 if (t instanceof IOException) { 380 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 String msg = "Filtered request failed."; 384 throw new ServletException(msg, t); 385 } 386 } 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 408 FilterChain chain = origChain; 409 410 FilterChainResolver resolver = getFilterChainResolver(); 411 if (resolver == null) { 412 log.debug("No FilterChainResolver configured. Returning original FilterChain."); 413 return origChain; 414 } 415 416 FilterChain resolved = resolver.getChain(request, response, origChain); 417 if (resolved != null) { 418 log.trace("Resolved a configured FilterChain for the current request."); 419 chain = resolved; 420 } else { 421 log.trace("No FilterChain configured for the current request. Using the default."); 422 } 423 424 return chain; 425 } 426 427 /** 428 * Executes a {@link FilterChain} for the given request. 429 * <p/> 430 * This implementation first delegates to 431 * <code>{@link #getExecutionChain(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) getExecutionChain}</code> 432 * to allow the application's Shiro configuration to determine exactly how the chain should execute. The resulting 433 * value from that call is then executed directly by calling the returned {@code FilterChain}'s 434 * {@link FilterChain#doFilter doFilter} method. That is: 435 * <pre> 436 * FilterChain chain = {@link #getExecutionChain}(request, response, origChain); 437 * chain.{@link FilterChain#doFilter doFilter}(request,response);</pre> 438 * 439 * @param request the incoming ServletRequest 440 * @param response the outgoing ServletResponse 441 * @param origChain the Servlet Container-provided chain that may be wrapped further by an application-configured 442 * chain of Filters. 443 * @throws IOException if the underlying {@code chain.doFilter} call results in an IOException 444 * @throws ServletException if the underlying {@code chain.doFilter} call results in a ServletException 445 * @since 1.0 446 */ 447 protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain) 448 throws IOException, ServletException { 449 FilterChain chain = getExecutionChain(request, response, origChain); 450 chain.doFilter(request, response); 451 } 452}