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 controll 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 * Private internal log instance. 051 */ 052 private static final Logger log = LoggerFactory.getLogger(OncePerRequestFilter.class); 053 054 /** 055 * Suffix that gets appended to the filter name for the "already filtered" request attribute. 056 * 057 * @see #getAlreadyFilteredAttributeName 058 */ 059 public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED"; 060 061 /** 062 * Determines generally if this filter should execute or let requests fall through to the next chain element. 063 * 064 * @see #isEnabled() 065 */ 066 private boolean enabled = true; //most filters wish to execute when configured, so default to true 067 068 /** 069 * Returns {@code true} if this filter should <em>generally</em><b>*</b> execute for any request, 070 * {@code false} if it should let the request/response pass through immediately to the next 071 * element in the {@link FilterChain}. The default value is {@code true}, as most filters would inherently need 072 * to execute when configured. 073 * <p/> 074 * <b>*</b> This configuration property is for general configuration for any request that comes through 075 * the filter. The 076 * {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse) isEnabled(request,response)} 077 * method actually determines whether or not if the filter is enabled based on the current request. 078 * 079 * @return {@code true} if this filter should <em>generally</em> execute, {@code false} if it should let the 080 * request/response pass through immediately to the next element in the {@link FilterChain}. 081 * @since 1.2 082 */ 083 public boolean isEnabled() { 084 return enabled; 085 } 086 087 /** 088 * Sets whether or not this filter <em>generally</em> executes for any request. See the 089 * {@link #isEnabled() isEnabled()} JavaDoc as to what <em>general</em> execution means. 090 * 091 * @param enabled whether or not this filter <em>generally</em> executes. 092 * @since 1.2 093 */ 094 public void setEnabled(boolean enabled) { 095 this.enabled = enabled; 096 } 097 098 /** 099 * 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}