View Javadoc
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 &quot;{@code .FILTERED}&quot;.
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 }