/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.shiro.web.servlet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.IOException; /** * Filter base class that guarantees to be just executed once per request, * on any servlet container. It provides a {@link #doFilterInternal} * method with HttpServletRequest and HttpServletResponse arguments. * <p/> * The {@link #getAlreadyFilteredAttributeName} method determines how * to identify that a request is already filtered. The default implementation * is based on the configured name of the concrete filter instance. * <h3>Controlling filter execution</h3> * 1.2 introduced the {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} method and * {@link #isEnabled()} property to allow explicit controll over whether the filter executes (or allows passthrough) * for any given request. * <p/> * <b>NOTE</b> This class was initially borrowed from the Spring framework but has continued modifications. * * @since 0.1 */ public abstract class OncePerRequestFilter extends NameableFilter { /** * Private internal log instance. */ private static final Logger log = LoggerFactory.getLogger(OncePerRequestFilter.class); /** * Suffix that gets appended to the filter name for the "already filtered" request attribute. * * @see #getAlreadyFilteredAttributeName */ public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED"; /** * Determines generally if this filter should execute or let requests fall through to the next chain element. * * @see #isEnabled() */ private boolean enabled = true; //most filters wish to execute when configured, so default to true /** * Returns {@code true} if this filter should <em>generally</em><b>*</b> execute for any request, * {@code false} if it should let the request/response pass through immediately to the next * element in the {@link FilterChain}. The default value is {@code true}, as most filters would inherently need * to execute when configured. * <p/> * <b>*</b> This configuration property is for general configuration for any request that comes through * the filter. The * {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse) isEnabled(request,response)} * method actually determines whether or not if the filter is enabled based on the current request. * * @return {@code true} if this filter should <em>generally</em> execute, {@code false} if it should let the * request/response pass through immediately to the next element in the {@link FilterChain}. * @since 1.2 */ public boolean isEnabled() { return enabled; } /** * Sets whether or not this filter <em>generally</em> executes for any request. See the * {@link #isEnabled() isEnabled()} JavaDoc as to what <em>general</em> execution means. * * @param enabled whether or not this filter <em>generally</em> executes. * @since 1.2 */ public void setEnabled(boolean enabled) { this.enabled = enabled; } /** * This {@code doFilter} implementation stores a request attribute for * "already filtered", proceeding without filtering again if the * attribute is already there. * * @see #getAlreadyFilteredAttributeName * @see #shouldNotFilter * @see #doFilterInternal */ public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName(); if ( request.getAttribute(alreadyFilteredAttributeName) != null ) { log.trace("Filter '{}' already executed. Proceeding without invoking this filter.", getName()); filterChain.doFilter(request, response); } else //noinspection deprecation if (/* added in 1.2: */ !isEnabled(request, response) || /* retain backwards compatibility: */ shouldNotFilter(request) ) { log.debug("Filter '{}' is not enabled for the current request. Proceeding without invoking this filter.", getName()); filterChain.doFilter(request, response); } else { // Do invoke this filter... log.trace("Filter '{}' not yet executed. Executing now.", getName()); request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE); try { doFilterInternal(request, response, filterChain); } finally { // Once the request has finished, we're done and we don't // need to mark as 'already filtered' any more. request.removeAttribute(alreadyFilteredAttributeName); } } } /** * Returns {@code true} if this filter should filter the specified request, {@code false} if it should let the * request/response pass through immediately to the next element in the {@code FilterChain}. * <p/> * This default implementation merely returns the value of {@link #isEnabled() isEnabled()}, which is * {@code true} by default (to ensure the filter always executes by default), but it can be overridden by * subclasses for request-specific behavior if necessary. For example, a filter could be enabled or disabled * based on the request path being accessed. * <p/> * <b>Helpful Hint:</b> if your subclass extends {@link org.apache.shiro.web.filter.PathMatchingFilter PathMatchingFilter}, * you may wish to instead override the * {@link org.apache.shiro.web.filter.PathMatchingFilter#isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse, String, Object) * PathMatchingFilter.isEnabled(request,response,path,pathSpecificConfig)} * method if you want to make your enable/disable decision based on any path-specific configuration. * * @param request the incoming servlet request * @param response the outbound servlet response * @return {@code true} if this filter should filter the specified request, {@code false} if it should let the * request/response pass through immediately to the next element in the {@code FilterChain}. * @throws IOException in the case of any IO error * @throws ServletException in the case of any error * @see org.apache.shiro.web.filter.PathMatchingFilter#isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse, String, Object) * @since 1.2 */ @SuppressWarnings({"UnusedParameters"}) protected boolean isEnabled(ServletRequest request, ServletResponse response) throws ServletException, IOException { return isEnabled(); } /** * Return name of the request attribute that identifies that a request has already been filtered. * <p/> * The default implementation takes the configured {@link #getName() name} and appends "{@code .FILTERED}". * If the filter is not fully initialized, it falls back to the implementation's class name. * * @return the name of the request attribute that identifies that a request has already been filtered. * @see #getName * @see #ALREADY_FILTERED_SUFFIX */ protected String getAlreadyFilteredAttributeName() { String name = getName(); if (name == null) { name = getClass().getName(); } return name + ALREADY_FILTERED_SUFFIX; } /** * Can be overridden in subclasses for custom filtering control, * returning <code>true</code> to avoid filtering of the given request. * <p>The default implementation always returns <code>false</code>. * * @param request current HTTP request * @return whether the given request should <i>not</i> be filtered * @throws ServletException in case of errors * @deprecated in favor of overriding {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} * for custom behavior. This method will be removed in Shiro 2.0. */ @Deprecated @SuppressWarnings({"UnusedDeclaration"}) protected boolean shouldNotFilter(ServletRequest request) throws ServletException { return false; } /** * Same contract as for * {@link #doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)}, * but guaranteed to be invoked only once per request. * * @param request incoming {@code ServletRequest} * @param response outgoing {@code ServletResponse} * @param chain the {@code FilterChain} to execute * @throws ServletException if there is a problem processing the request * @throws IOException if there is an I/O problem processing the request */ protected abstract void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException; }