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.slf4j.Logger; 022import org.slf4j.LoggerFactory; 023 024import javax.servlet.FilterChain; 025import javax.servlet.ServletException; 026import javax.servlet.ServletRequest; 027import javax.servlet.ServletResponse; 028import java.io.IOException; 029 030/** 031 * Filter base class that guarantees to be just executed once per request, 032 * on any servlet container. It provides a {@link #doFilterInternal} 033 * method with HttpServletRequest and HttpServletResponse arguments. 034 * <p/> 035 * The {@link #getAlreadyFilteredAttributeName} method determines how 036 * to identify that a request is already filtered. The default implementation 037 * is based on the configured name of the concrete filter instance. 038 * <h3>Controlling filter execution</h3> 039 * 1.2 introduced the {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} method and 040 * {@link #isEnabled()} property to allow explicit control over whether the filter executes (or allows passthrough) 041 * for any given request. 042 * <p/> 043 * <b>NOTE</b> This class was initially borrowed from the Spring framework but has continued modifications. 044 * 045 * @since 0.1 046 */ 047public abstract class OncePerRequestFilter extends NameableFilter { 048 049 /** 050 * Suffix that gets appended to the filter name for the "already filtered" request attribute. 051 * 052 * @see #getAlreadyFilteredAttributeName 053 */ 054 public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED"; 055 056 /** 057 * Private internal log instance. 058 */ 059 private static final Logger LOGGER = LoggerFactory.getLogger(OncePerRequestFilter.class); 060 061 /** 062 * Determines generally if this filter should execute or let requests fall through to the next chain element. 063 * most filters wish to execute when configured, so default to true 064 * 065 * @see #isEnabled() 066 */ 067 private boolean enabled = true; 068 069 /** 070 * Determines if the filter's once per request functionality is enabled, defaults to false. It is recommended 071 * to leave this disabled if you are using a {@link javax.servlet.RequestDispatcher RequestDispatcher} to forward 072 * or include request (JSP tags, programmatically, or via a framework). 073 */ 074 private boolean filterOncePerRequest; 075 076 /** 077 * Returns {@code true} if this filter should <em>generally</em><b>*</b> execute for any request, 078 * {@code false} if it should let the request/response pass through immediately to the next 079 * element in the {@link FilterChain}. The default value is {@code true}, as most filters would inherently need 080 * to execute when configured. 081 * <p/> 082 * <b>*</b> This configuration property is for general configuration for any request that comes through 083 * the filter. The 084 * {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse) isEnabled(request,response)} 085 * method actually determines whether or not if the filter is enabled based on the current request. 086 * 087 * @return {@code true} if this filter should <em>generally</em> execute, {@code false} if it should let the 088 * request/response pass through immediately to the next element in the {@link FilterChain}. 089 * @since 1.2 090 */ 091 public boolean isEnabled() { 092 return enabled; 093 } 094 095 /** 096 * Sets whether or not this filter <em>generally</em> executes for any request. See the 097 * {@link #isEnabled() isEnabled()} JavaDoc as to what <em>general</em> execution means. 098 * 099 * @param enabled whether or not this filter <em>generally</em> executes. 100 * @since 1.2 101 */ 102 public void setEnabled(boolean enabled) { 103 this.enabled = enabled; 104 } 105 106 /** 107 * Returns {@code true} if this filter should only execute once per request. If set to {@code false} this filter 108 * will execute each time it is invoked. 109 * 110 * @return {@code true} if this filter should only execute once per request. 111 * @since 1.10 112 */ 113 public boolean isFilterOncePerRequest() { 114 return filterOncePerRequest; 115 } 116 117 /** 118 * Sets whether this filter executes once per request or for every invocation of the filter. It is recommended 119 * to leave this disabled if you are using a {@link javax.servlet.RequestDispatcher RequestDispatcher} to forward 120 * or include request (JSP tags, programmatically, or via a framework). 121 * 122 * @param filterOncePerRequest Whether this filter executes once per request. 123 * @since 1.10 124 */ 125 public void setFilterOncePerRequest(boolean filterOncePerRequest) { 126 this.filterOncePerRequest = filterOncePerRequest; 127 } 128 129 /** 130 * This {@code doFilter} implementation stores a request attribute for 131 * "already filtered", proceeding without filtering again if the 132 * attribute is already there. 133 * 134 * @see #getAlreadyFilteredAttributeName 135 * @see #shouldNotFilter 136 * @see #doFilterInternal 137 */ 138 public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) 139 throws ServletException, IOException { 140 String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName(); 141 if (request.getAttribute(alreadyFilteredAttributeName) != null && filterOncePerRequest) { 142 LOGGER.trace("Filter '{}' already executed. Proceeding without invoking this filter.", getName()); 143 filterChain.doFilter(request, response); 144 //noinspection deprecation 145 } else if (/* added in 1.2: */ !isEnabled(request, response) 146 || /* retain backwards compatibility: */ shouldNotFilter(request)) { 147 LOGGER.debug("Filter '{}' is not enabled for the current request. Proceeding without invoking this filter.", 148 getName()); 149 filterChain.doFilter(request, response); 150 } else { 151 // Do invoke this filter... 152 LOGGER.trace("Filter '{}' not yet executed. Executing now.", getName()); 153 request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE); 154 155 try { 156 doFilterInternal(request, response, filterChain); 157 } finally { 158 // Once the request has finished, we're done and we don't 159 // need to mark as 'already filtered' any more. 160 request.removeAttribute(alreadyFilteredAttributeName); 161 } 162 } 163 } 164 165 /** 166 * Returns {@code true} if this filter should filter the specified request, {@code false} if it should let the 167 * request/response pass through immediately to the next element in the {@code FilterChain}. 168 * <p/> 169 * This default implementation merely returns the value of {@link #isEnabled() isEnabled()}, which is 170 * {@code true} by default (to ensure the filter always executes by default), but it can be overridden by 171 * subclasses for request-specific behavior if necessary. For example, a filter could be enabled or disabled 172 * based on the request path being accessed. 173 * <p/> 174 * <b>Helpful Hint:</b> if your subclass extends {@link org.apache.shiro.web.filter.PathMatchingFilter PathMatchingFilter}, 175 * you may wish to instead override the 176 * method if you want to make your enable/disable decision based on any path-specific configuration. 177 * 178 * @param request the incoming servlet request 179 * @param response the outbound servlet response 180 * @return {@code true} if this filter should filter the specified request, {@code false} if it should let the 181 * request/response pass through immediately to the next element in the {@code FilterChain}. 182 * @throws IOException in the case of any IO error 183 * @throws ServletException in the case of any error 184 * @since 1.2 185 */ 186 @SuppressWarnings({"UnusedParameters"}) 187 protected boolean isEnabled(ServletRequest request, ServletResponse response) throws ServletException, IOException { 188 return isEnabled(); 189 } 190 191 /** 192 * Return name of the request attribute that identifies that a request has already been filtered. 193 * <p/> 194 * The default implementation takes the configured {@link #getName() name} and appends "{@code .FILTERED}". 195 * If the filter is not fully initialized, it falls back to the implementation's class name. 196 * 197 * @return the name of the request attribute that identifies that a request has already been filtered. 198 * @see #getName 199 * @see #ALREADY_FILTERED_SUFFIX 200 */ 201 protected String getAlreadyFilteredAttributeName() { 202 String name = getName(); 203 if (name == null) { 204 name = getClass().getName(); 205 } 206 return name + ALREADY_FILTERED_SUFFIX; 207 } 208 209 /** 210 * Can be overridden in subclasses for custom filtering control, 211 * returning <code>true</code> to avoid filtering of the given request. 212 * <p>The default implementation always returns <code>false</code>. 213 * 214 * @param request current HTTP request 215 * @return whether the given request should <i>not</i> be filtered 216 * @throws ServletException in case of errors 217 * @deprecated in favor of overriding {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} 218 * for custom behavior. This method will be removed in Shiro 2.0. 219 */ 220 @Deprecated 221 @SuppressWarnings({"UnusedDeclaration"}) 222 protected boolean shouldNotFilter(ServletRequest request) throws ServletException { 223 return false; 224 } 225 226 227 /** 228 * Same contract as for 229 * {@link #doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)}, 230 * but guaranteed to be invoked only once per request. 231 * 232 * @param request incoming {@code ServletRequest} 233 * @param response outgoing {@code ServletResponse} 234 * @param chain the {@code FilterChain} to execute 235 * @throws ServletException if there is a problem processing the request 236 * @throws IOException if there is an I/O problem processing the request 237 */ 238 protected abstract void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) 239 throws ServletException, IOException; 240}