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.slf4j.Logger; 22 import org.slf4j.LoggerFactory; 23 24 import javax.servlet.FilterChain; 25 import javax.servlet.ServletException; 26 import javax.servlet.ServletRequest; 27 import javax.servlet.ServletResponse; 28 import java.io.IOException; 29 30 /** 31 * Filter base class that guarantees to be just executed once per request, 32 * on any servlet container. It provides a {@link #doFilterInternal} 33 * method with HttpServletRequest and HttpServletResponse arguments. 34 * <p/> 35 * The {@link #getAlreadyFilteredAttributeName} method determines how 36 * to identify that a request is already filtered. The default implementation 37 * is based on the configured name of the concrete filter instance. 38 * <h3>Controlling filter execution</h3> 39 * 1.2 introduced the {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} method and 40 * {@link #isEnabled()} property to allow explicit controll over whether the filter executes (or allows passthrough) 41 * for any given request. 42 * <p/> 43 * <b>NOTE</b> This class was initially borrowed from the Spring framework but has continued modifications. 44 * 45 * @since 0.1 46 */ 47 public abstract class OncePerRequestFilter extends NameableFilter { 48 49 /** 50 * Private internal log instance. 51 */ 52 private static final Logger log = LoggerFactory.getLogger(OncePerRequestFilter.class); 53 54 /** 55 * Suffix that gets appended to the filter name for the "already filtered" request attribute. 56 * 57 * @see #getAlreadyFilteredAttributeName 58 */ 59 public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED"; 60 61 /** 62 * Determines generally if this filter should execute or let requests fall through to the next chain element. 63 * 64 * @see #isEnabled() 65 */ 66 private boolean enabled = true; //most filters wish to execute when configured, so default to true 67 68 /** 69 * Returns {@code true} if this filter should <em>generally</em><b>*</b> execute for any request, 70 * {@code false} if it should let the request/response pass through immediately to the next 71 * element in the {@link FilterChain}. The default value is {@code true}, as most filters would inherently need 72 * to execute when configured. 73 * <p/> 74 * <b>*</b> This configuration property is for general configuration for any request that comes through 75 * the filter. The 76 * {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse) isEnabled(request,response)} 77 * method actually determines whether or not if the filter is enabled based on the current request. 78 * 79 * @return {@code true} if this filter should <em>generally</em> execute, {@code false} if it should let the 80 * request/response pass through immediately to the next element in the {@link FilterChain}. 81 * @since 1.2 82 */ 83 public boolean isEnabled() { 84 return enabled; 85 } 86 87 /** 88 * Sets whether or not this filter <em>generally</em> executes for any request. See the 89 * {@link #isEnabled() isEnabled()} JavaDoc as to what <em>general</em> execution means. 90 * 91 * @param enabled whether or not this filter <em>generally</em> executes. 92 * @since 1.2 93 */ 94 public void setEnabled(boolean enabled) { 95 this.enabled = enabled; 96 } 97 98 /** 99 * This {@code doFilter} implementation stores a request attribute for 100 * "already filtered", proceeding without filtering again if the 101 * attribute is already there. 102 * 103 * @see #getAlreadyFilteredAttributeName 104 * @see #shouldNotFilter 105 * @see #doFilterInternal 106 */ 107 public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) 108 throws ServletException, IOException { 109 String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName(); 110 if ( request.getAttribute(alreadyFilteredAttributeName) != null ) { 111 log.trace("Filter '{}' already executed. Proceeding without invoking this filter.", getName()); 112 filterChain.doFilter(request, response); 113 } else //noinspection deprecation 114 if (/* added in 1.2: */ !isEnabled(request, response) || 115 /* retain backwards compatibility: */ shouldNotFilter(request) ) { 116 log.debug("Filter '{}' is not enabled for the current request. Proceeding without invoking this filter.", 117 getName()); 118 filterChain.doFilter(request, response); 119 } else { 120 // Do invoke this filter... 121 log.trace("Filter '{}' not yet executed. Executing now.", getName()); 122 request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE); 123 124 try { 125 doFilterInternal(request, response, filterChain); 126 } finally { 127 // Once the request has finished, we're done and we don't 128 // need to mark as 'already filtered' any more. 129 request.removeAttribute(alreadyFilteredAttributeName); 130 } 131 } 132 } 133 134 /** 135 * Returns {@code true} if this filter should filter the specified request, {@code false} if it should let the 136 * request/response pass through immediately to the next element in the {@code FilterChain}. 137 * <p/> 138 * This default implementation merely returns the value of {@link #isEnabled() isEnabled()}, which is 139 * {@code true} by default (to ensure the filter always executes by default), but it can be overridden by 140 * subclasses for request-specific behavior if necessary. For example, a filter could be enabled or disabled 141 * based on the request path being accessed. 142 * <p/> 143 * <b>Helpful Hint:</b> if your subclass extends {@link org.apache.shiro.web.filter.PathMatchingFilter PathMatchingFilter}, 144 * you may wish to instead override the 145 * {@link org.apache.shiro.web.filter.PathMatchingFilter#isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse, String, Object) 146 * PathMatchingFilter.isEnabled(request,response,path,pathSpecificConfig)} 147 * method if you want to make your enable/disable decision based on any path-specific configuration. 148 * 149 * @param request the incoming servlet request 150 * @param response the outbound servlet response 151 * @return {@code true} if this filter should filter the specified request, {@code false} if it should let the 152 * request/response pass through immediately to the next element in the {@code FilterChain}. 153 * @throws IOException in the case of any IO error 154 * @throws ServletException in the case of any error 155 * @see org.apache.shiro.web.filter.PathMatchingFilter#isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse, String, Object) 156 * @since 1.2 157 */ 158 @SuppressWarnings({"UnusedParameters"}) 159 protected boolean isEnabled(ServletRequest request, ServletResponse response) throws ServletException, IOException { 160 return isEnabled(); 161 } 162 163 /** 164 * Return name of the request attribute that identifies that a request has already been filtered. 165 * <p/> 166 * The default implementation takes the configured {@link #getName() name} and appends "{@code .FILTERED}". 167 * If the filter is not fully initialized, it falls back to the implementation's class name. 168 * 169 * @return the name of the request attribute that identifies that a request has already been filtered. 170 * @see #getName 171 * @see #ALREADY_FILTERED_SUFFIX 172 */ 173 protected String getAlreadyFilteredAttributeName() { 174 String name = getName(); 175 if (name == null) { 176 name = getClass().getName(); 177 } 178 return name + ALREADY_FILTERED_SUFFIX; 179 } 180 181 /** 182 * Can be overridden in subclasses for custom filtering control, 183 * returning <code>true</code> to avoid filtering of the given request. 184 * <p>The default implementation always returns <code>false</code>. 185 * 186 * @param request current HTTP request 187 * @return whether the given request should <i>not</i> be filtered 188 * @throws ServletException in case of errors 189 * @deprecated in favor of overriding {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} 190 * for custom behavior. This method will be removed in Shiro 2.0. 191 */ 192 @Deprecated 193 @SuppressWarnings({"UnusedDeclaration"}) 194 protected boolean shouldNotFilter(ServletRequest request) throws ServletException { 195 return false; 196 } 197 198 199 /** 200 * Same contract as for 201 * {@link #doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)}, 202 * but guaranteed to be invoked only once per request. 203 * 204 * @param request incoming {@code ServletRequest} 205 * @param response outgoing {@code ServletResponse} 206 * @param chain the {@code FilterChain} to execute 207 * @throws ServletException if there is a problem processing the request 208 * @throws IOException if there is an I/O problem processing the request 209 */ 210 protected abstract void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) 211 throws ServletException, IOException; 212 }