You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shiro.apache.org by bd...@apache.org on 2022/10/11 16:22:24 UTC

[shiro] 01/03: Allow for direct configuration of ShiroFilter through WebEnvironment

This is an automated email from the ASF dual-hosted git repository.

bdemers pushed a commit to branch 1.10.x
in repository https://gitbox.apache.org/repos/asf/shiro.git

commit 28e10e0ca1cdcd2cede86802fde8464b29265fc8
Author: Brian Demers <bd...@apache.org>
AuthorDate: Tue Aug 16 17:15:46 2022 -0400

    Allow for direct configuration of ShiroFilter through WebEnvironment
    
    Adds new ShiroFilterConfiguration class exposed through the WebEnvironment
    This class allows for confiuration of the Shiro filter through various config mechanisms (Ini, Guice, Spring, etc)
    This makes it easer to enable a static security manager when using Shiro's Servlet fragment, as now a web.xml is not required
---
 pom.xml                                            |  1 +
 samples/servlet-plugin/pom.xml                     |  9 +--
 .../japicmp/postAnalysisScript.groovy              | 74 +++++++++---------
 .../apache/shiro/guice/web/GuiceShiroFilter.java   |  4 +-
 .../shiro/guice/web/WebGuiceEnvironment.java       | 10 ++-
 .../shiro/guice/web/GuiceShiroFilterTest.java      | 15 +++-
 .../apache/shiro/guice/web/ShiroWebModuleTest.java |  5 +-
 .../shiro/guice/web/WebGuiceEnvironmentTest.java   |  4 +-
 .../shiro/spring/web/ShiroFilterFactoryBean.java   | 34 ++++++++-
 .../AbstractShiroWebFilterConfiguration.java       | 11 +++
 .../shiro/web/config/ShiroFilterConfiguration.java | 87 ++++++++++++++++++++++
 .../shiro/web/env/DefaultWebEnvironment.java       | 15 ++++
 .../apache/shiro/web/env/IniWebEnvironment.java    | 11 +++
 .../shiro/web/env/MutableWebEnvironment.java       |  8 ++
 .../org/apache/shiro/web/env/WebEnvironment.java   | 11 +++
 .../shiro/web/servlet/AbstractShiroFilter.java     |  8 ++
 .../shiro/web/servlet/OncePerRequestFilter.java    | 33 +++++++-
 .../org/apache/shiro/web/servlet/ShiroFilter.java  |  2 +
 .../shiro/web/env/IniWebEnvironmentTest.groovy     |  9 ++-
 .../apache/shiro/web/env/MockWebEnvironment.groovy |  6 ++
 .../shiro/web/servlet/ShiroFilterTest.groovy       | 68 ++++++++++++++++-
 .../apache/shiro/web/env/WebEnvironmentStub.java   | 13 ++++
 .../web/servlet/OncePerRequestFilterTest.java      | 52 ++++++++-----
 23 files changed, 407 insertions(+), 83 deletions(-)

diff --git a/pom.xml b/pom.xml
index 494418bb..5be23a56 100644
--- a/pom.xml
+++ b/pom.xml
@@ -419,6 +419,7 @@
                             <onlyModified>true</onlyModified>
                             <breakBuildOnBinaryIncompatibleModifications>true</breakBuildOnBinaryIncompatibleModifications>
                             <breakBuildBasedOnSemanticVersioning>true</breakBuildBasedOnSemanticVersioning>
+                            <postAnalysisScript>${root.dir}/src/japicmp/postAnalysisScript.groovy</postAnalysisScript>
                         </parameter>
                     </configuration>
                 </plugin>
diff --git a/samples/servlet-plugin/pom.xml b/samples/servlet-plugin/pom.xml
index 061a6806..b2315f9d 100644
--- a/samples/servlet-plugin/pom.xml
+++ b/samples/servlet-plugin/pom.xml
@@ -106,13 +106,8 @@
             <scope>runtime</scope>
         </dependency>
         <dependency>
-            <groupId>org.apache.logging.log4j</groupId>
-            <artifactId>log4j-slf4j-impl</artifactId>
-            <scope>runtime</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.logging.log4j</groupId>
-            <artifactId>log4j-core</artifactId>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
             <scope>runtime</scope>
         </dependency>
         <dependency>
diff --git a/support/guice/src/main/java/org/apache/shiro/guice/web/GuiceShiroFilter.java b/src/japicmp/postAnalysisScript.groovy
similarity index 50%
copy from support/guice/src/main/java/org/apache/shiro/guice/web/GuiceShiroFilter.java
copy to src/japicmp/postAnalysisScript.groovy
index e58449f1..522e2393 100644
--- a/support/guice/src/main/java/org/apache/shiro/guice/web/GuiceShiroFilter.java
+++ b/src/japicmp/postAnalysisScript.groovy
@@ -1,38 +1,36 @@
-/*
- * 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.guice.web;
-
-import org.apache.shiro.web.filter.mgt.FilterChainResolver;
-import org.apache.shiro.web.mgt.WebSecurityManager;
-import org.apache.shiro.web.servlet.AbstractShiroFilter;
-
-import javax.inject.Inject;
-
-/**
- * Shiro filter that is managed by and receives its filter chain configurations from Guice.  The convenience method to
- * map this filter to your application is
- * {@link ShiroWebModule#bindGuiceFilter(com.google.inject.Binder) bindGuiceFilter}.
- */
-public class GuiceShiroFilter extends AbstractShiroFilter {
-    @Inject
-    GuiceShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver filterChainResolver) {
-        this.setSecurityManager(webSecurityManager);
-        this.setFilterChainResolver(filterChainResolver);
-    }
-}
+/*
+ * 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.
+ */
+
+import static japicmp.model.JApiCompatibilityChange.*
+import static japicmp.model.JApiChangeStatus.*
+
+def it = jApiClasses.iterator()
+while (it.hasNext()) {
+    def jApiClass = it.next()
+    // filter out interfaces changes that are default methods
+    if (jApiClass.getChangeStatus() != UNCHANGED) {
+        def methodIt = jApiClass.getMethods().iterator()
+        while (methodIt.hasNext()) {
+            def method = methodIt.next()
+            def methodChanges = method.getCompatibilityChanges()
+            methodChanges.remove(METHOD_NEW_DEFAULT)
+        }
+    }
+}
+return jApiClasses
\ No newline at end of file
diff --git a/support/guice/src/main/java/org/apache/shiro/guice/web/GuiceShiroFilter.java b/support/guice/src/main/java/org/apache/shiro/guice/web/GuiceShiroFilter.java
index e58449f1..ae99b2e3 100644
--- a/support/guice/src/main/java/org/apache/shiro/guice/web/GuiceShiroFilter.java
+++ b/support/guice/src/main/java/org/apache/shiro/guice/web/GuiceShiroFilter.java
@@ -18,6 +18,7 @@
  */
 package org.apache.shiro.guice.web;
 
+import org.apache.shiro.web.config.ShiroFilterConfiguration;
 import org.apache.shiro.web.filter.mgt.FilterChainResolver;
 import org.apache.shiro.web.mgt.WebSecurityManager;
 import org.apache.shiro.web.servlet.AbstractShiroFilter;
@@ -31,8 +32,9 @@ import javax.inject.Inject;
  */
 public class GuiceShiroFilter extends AbstractShiroFilter {
     @Inject
-    GuiceShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver filterChainResolver) {
+    GuiceShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver filterChainResolver, ShiroFilterConfiguration filterConfiguration) {
         this.setSecurityManager(webSecurityManager);
         this.setFilterChainResolver(filterChainResolver);
+        this.setShiroFilterConfiguration(filterConfiguration);
     }
 }
diff --git a/support/guice/src/main/java/org/apache/shiro/guice/web/WebGuiceEnvironment.java b/support/guice/src/main/java/org/apache/shiro/guice/web/WebGuiceEnvironment.java
index 591d1c65..a1699154 100644
--- a/support/guice/src/main/java/org/apache/shiro/guice/web/WebGuiceEnvironment.java
+++ b/support/guice/src/main/java/org/apache/shiro/guice/web/WebGuiceEnvironment.java
@@ -21,6 +21,7 @@ package org.apache.shiro.guice.web;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.web.config.ShiroFilterConfiguration;
 import org.apache.shiro.web.env.EnvironmentLoaderListener;
 import org.apache.shiro.web.env.WebEnvironment;
 import org.apache.shiro.web.filter.mgt.FilterChainResolver;
@@ -35,11 +36,14 @@ class WebGuiceEnvironment implements WebEnvironment {
     private ServletContext servletContext;
     private WebSecurityManager securityManager;
 
+    private ShiroFilterConfiguration filterConfiguration;
+
     @Inject
-    WebGuiceEnvironment(FilterChainResolver filterChainResolver, @Named(ShiroWebModule.NAME) ServletContext servletContext, WebSecurityManager securityManager) {
+    WebGuiceEnvironment(FilterChainResolver filterChainResolver, @Named(ShiroWebModule.NAME) ServletContext servletContext, WebSecurityManager securityManager, ShiroFilterConfiguration filterConfiguration) {
         this.filterChainResolver = filterChainResolver;
         this.servletContext = servletContext;
         this.securityManager = securityManager;
+        this.filterConfiguration = filterConfiguration;
 
         servletContext.setAttribute(EnvironmentLoaderListener.ENVIRONMENT_ATTRIBUTE_KEY, this);
     }
@@ -59,4 +63,8 @@ class WebGuiceEnvironment implements WebEnvironment {
     public SecurityManager getSecurityManager() {
         return securityManager;
     }
+
+    public ShiroFilterConfiguration getShiroFilterConfiguration() {
+        return filterConfiguration;
+    }
 }
diff --git a/support/guice/src/test/java/org/apache/shiro/guice/web/GuiceShiroFilterTest.java b/support/guice/src/test/java/org/apache/shiro/guice/web/GuiceShiroFilterTest.java
index dbc1017e..1e01a113 100644
--- a/support/guice/src/test/java/org/apache/shiro/guice/web/GuiceShiroFilterTest.java
+++ b/support/guice/src/test/java/org/apache/shiro/guice/web/GuiceShiroFilterTest.java
@@ -19,13 +19,13 @@
 package org.apache.shiro.guice.web;
 
 import com.google.inject.spi.InjectionPoint;
+import org.apache.shiro.web.config.ShiroFilterConfiguration;
 import org.apache.shiro.web.filter.mgt.FilterChainResolver;
 import org.apache.shiro.web.mgt.WebSecurityManager;
 import org.junit.Test;
 
-import static org.easymock.EasyMock.createMock;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.fail;
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
 
 public class GuiceShiroFilterTest {
 
@@ -42,10 +42,17 @@ public class GuiceShiroFilterTest {
     public void testConstructor() {
         WebSecurityManager securityManager = createMock(WebSecurityManager.class);
         FilterChainResolver filterChainResolver = createMock(FilterChainResolver.class);
+        ShiroFilterConfiguration filterConfiguration = createMock(ShiroFilterConfiguration.class);
+        expect(filterConfiguration.isStaticSecurityManagerEnabled()).andReturn(true);
+        expect(filterConfiguration.isFilterOncePerRequest()).andReturn(false);
 
-        GuiceShiroFilter underTest = new GuiceShiroFilter(securityManager, filterChainResolver);
+        replay(securityManager, filterChainResolver, filterConfiguration);
+
+        GuiceShiroFilter underTest = new GuiceShiroFilter(securityManager, filterChainResolver, filterConfiguration);
 
         assertSame(securityManager, underTest.getSecurityManager());
         assertSame(filterChainResolver, underTest.getFilterChainResolver());
+        assertTrue(underTest.isStaticSecurityManagerEnabled());
+        assertFalse(underTest.isFilterOncePerRequest());
     }
 }
diff --git a/support/guice/src/test/java/org/apache/shiro/guice/web/ShiroWebModuleTest.java b/support/guice/src/test/java/org/apache/shiro/guice/web/ShiroWebModuleTest.java
index 6de5a751..c24359a4 100644
--- a/support/guice/src/test/java/org/apache/shiro/guice/web/ShiroWebModuleTest.java
+++ b/support/guice/src/test/java/org/apache/shiro/guice/web/ShiroWebModuleTest.java
@@ -30,6 +30,7 @@ import org.apache.shiro.env.Environment;
 import org.apache.shiro.mgt.SecurityManager;
 import org.apache.shiro.realm.Realm;
 import org.apache.shiro.session.mgt.SessionManager;
+import org.apache.shiro.web.config.ShiroFilterConfiguration;
 import org.apache.shiro.web.env.EnvironmentLoader;
 import org.apache.shiro.web.env.WebEnvironment;
 import org.apache.shiro.web.filter.InvalidRequestFilter;
@@ -487,8 +488,8 @@ public class ShiroWebModuleTest {
 
     public static class MyWebEnvironment extends WebGuiceEnvironment {
         @Inject
-        MyWebEnvironment(FilterChainResolver filterChainResolver, @Named(ShiroWebModule.NAME) ServletContext servletContext, WebSecurityManager securityManager) {
-            super(filterChainResolver, servletContext, securityManager);
+        MyWebEnvironment(FilterChainResolver filterChainResolver, @Named(ShiroWebModule.NAME) ServletContext servletContext, WebSecurityManager securityManager, ShiroFilterConfiguration filterConfiguration) {
+            super(filterChainResolver, servletContext, securityManager, filterConfiguration);
         }
     }
 
diff --git a/support/guice/src/test/java/org/apache/shiro/guice/web/WebGuiceEnvironmentTest.java b/support/guice/src/test/java/org/apache/shiro/guice/web/WebGuiceEnvironmentTest.java
index 77fada3f..f5189f2e 100644
--- a/support/guice/src/test/java/org/apache/shiro/guice/web/WebGuiceEnvironmentTest.java
+++ b/support/guice/src/test/java/org/apache/shiro/guice/web/WebGuiceEnvironmentTest.java
@@ -19,6 +19,7 @@
 package org.apache.shiro.guice.web;
 
 import com.google.inject.spi.InjectionPoint;
+import org.apache.shiro.web.config.ShiroFilterConfiguration;
 import org.apache.shiro.web.env.EnvironmentLoaderListener;
 import org.apache.shiro.web.filter.mgt.FilterChainResolver;
 import org.apache.shiro.web.mgt.WebSecurityManager;
@@ -47,13 +48,14 @@ public class WebGuiceEnvironmentTest {
         WebSecurityManager securityManager = createMock(WebSecurityManager.class);
         FilterChainResolver filterChainResolver = createMock(FilterChainResolver.class);
         ServletContext servletContext = createMock(ServletContext.class);
+        ShiroFilterConfiguration filterConfiguration = createMock(ShiroFilterConfiguration.class);
 
         Capture<WebGuiceEnvironment> capture = Capture.newInstance();
         servletContext.setAttribute(eq(EnvironmentLoaderListener.ENVIRONMENT_ATTRIBUTE_KEY), and(anyObject(WebGuiceEnvironment.class), capture(capture)));
 
         replay(servletContext, securityManager, filterChainResolver);
 
-        WebGuiceEnvironment underTest = new WebGuiceEnvironment(filterChainResolver, servletContext, securityManager);
+        WebGuiceEnvironment underTest = new WebGuiceEnvironment(filterChainResolver, servletContext, securityManager, filterConfiguration);
 
         assertSame(securityManager, underTest.getSecurityManager());
         assertSame(filterChainResolver, underTest.getFilterChainResolver());
diff --git a/support/spring/src/main/java/org/apache/shiro/spring/web/ShiroFilterFactoryBean.java b/support/spring/src/main/java/org/apache/shiro/spring/web/ShiroFilterFactoryBean.java
index ddc9f865..45f3e363 100644
--- a/support/spring/src/main/java/org/apache/shiro/spring/web/ShiroFilterFactoryBean.java
+++ b/support/spring/src/main/java/org/apache/shiro/spring/web/ShiroFilterFactoryBean.java
@@ -24,6 +24,7 @@ import org.apache.shiro.util.CollectionUtils;
 import org.apache.shiro.util.Nameable;
 import org.apache.shiro.util.StringUtils;
 import org.apache.shiro.web.config.IniFilterChainResolverFactory;
+import org.apache.shiro.web.config.ShiroFilterConfiguration;
 import org.apache.shiro.web.filter.AccessControlFilter;
 import org.apache.shiro.web.filter.InvalidRequestFilter;
 import org.apache.shiro.web.filter.authc.AuthenticationFilter;
@@ -35,6 +36,7 @@ import org.apache.shiro.web.filter.mgt.FilterChainResolver;
 import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
 import org.apache.shiro.web.mgt.WebSecurityManager;
 import org.apache.shiro.web.servlet.AbstractShiroFilter;
+import org.apache.shiro.web.servlet.OncePerRequestFilter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeansException;
@@ -135,15 +137,18 @@ public class ShiroFilterFactoryBean implements FactoryBean<AbstractShiroFilter>,
 
     private AbstractShiroFilter instance;
 
+    private ShiroFilterConfiguration filterConfiguration;
+
     public ShiroFilterFactoryBean() {
         this.filters = new LinkedHashMap<String, Filter>();
         this.globalFilters = new ArrayList<>();
         this.globalFilters.add(DefaultFilter.invalidRequest.name());
         this.filterChainDefinitionMap = new LinkedHashMap<String, String>(); //order matters!
+        this.filterConfiguration = new ShiroFilterConfiguration();
     }
 
     /**
-     * Sets the application {@code SecurityManager} instance to be used by the constructed Shiro Filter.  This is a
+     * Gets the application {@code SecurityManager} instance to be used by the constructed Shiro Filter.  This is a
      * required property - failure to set it will throw an initialization exception.
      *
      * @return the application {@code SecurityManager} instance to be used by the constructed Shiro Filter.
@@ -162,6 +167,24 @@ public class ShiroFilterFactoryBean implements FactoryBean<AbstractShiroFilter>,
         this.securityManager = securityManager;
     }
 
+    /**
+     * Gets the application {@code ShiroFilterConfiguration} instance to be used by the constructed Shiro Filter.
+     *
+     * @return the application {@code ShiroFilterConfiguration} instance to be used by the constructed Shiro Filter.
+     */
+    public ShiroFilterConfiguration getShiroFilterConfiguration() {
+        return filterConfiguration;
+    }
+
+    /**
+     * Sets the application {@code ShiroFilterConfiguration} instance to be used by the constructed Shiro Filter.
+     *
+     * @param filterConfiguration the application {@code SecurityManager} instance to be used by the constructed Shiro Filter.
+     */
+    public void setShiroFilterConfiguration(ShiroFilterConfiguration filterConfiguration) {
+        this.filterConfiguration = filterConfiguration;
+    }
+
     /**
      * Returns the application's login URL to be assigned to all acquired Filters that subclass
      * {@link AccessControlFilter} or {@code null} if no value should be assigned globally. The default value
@@ -468,7 +491,7 @@ public class ShiroFilterFactoryBean implements FactoryBean<AbstractShiroFilter>,
         //FilterChainResolver.  It doesn't matter that the instance is an anonymous inner class
         //here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts
         //injection of the SecurityManager and FilterChainResolver:
-        return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
+        return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver, getShiroFilterConfiguration());
     }
 
     private void applyLoginUrlIfNecessary(Filter filter) {
@@ -511,6 +534,10 @@ public class ShiroFilterFactoryBean implements FactoryBean<AbstractShiroFilter>,
         applyLoginUrlIfNecessary(filter);
         applySuccessUrlIfNecessary(filter);
         applyUnauthorizedUrlIfNecessary(filter);
+
+        if (filter instanceof OncePerRequestFilter) {
+            ((OncePerRequestFilter) filter).setFilterOncePerRequest(filterConfiguration.isFilterOncePerRequest());
+        }
     }
 
     /**
@@ -549,12 +576,13 @@ public class ShiroFilterFactoryBean implements FactoryBean<AbstractShiroFilter>,
      */
     private static final class SpringShiroFilter extends AbstractShiroFilter {
 
-        protected SpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {
+        protected SpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver, ShiroFilterConfiguration filterConfiguration) {
             super();
             if (webSecurityManager == null) {
                 throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
             }
             setSecurityManager(webSecurityManager);
+            setShiroFilterConfiguration(filterConfiguration);
 
             if (resolver != null) {
                 setFilterChainResolver(resolver);
diff --git a/support/spring/src/main/java/org/apache/shiro/spring/web/config/AbstractShiroWebFilterConfiguration.java b/support/spring/src/main/java/org/apache/shiro/spring/web/config/AbstractShiroWebFilterConfiguration.java
index 685d63df..4547c3e2 100644
--- a/support/spring/src/main/java/org/apache/shiro/spring/web/config/AbstractShiroWebFilterConfiguration.java
+++ b/support/spring/src/main/java/org/apache/shiro/spring/web/config/AbstractShiroWebFilterConfiguration.java
@@ -20,6 +20,7 @@ package org.apache.shiro.spring.web.config;
 
 import org.apache.shiro.mgt.SecurityManager;
 import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
+import org.apache.shiro.web.config.ShiroFilterConfiguration;
 import org.apache.shiro.web.filter.mgt.DefaultFilter;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
@@ -40,6 +41,9 @@ public class AbstractShiroWebFilterConfiguration {
     @Autowired
     protected ShiroFilterChainDefinition shiroFilterChainDefinition;
 
+    @Autowired(required = false)
+    protected ShiroFilterConfiguration shiroFilterConfiguration;
+
     @Autowired(required = false)
     protected Map<String, Filter> filterMap;
 
@@ -56,6 +60,12 @@ public class AbstractShiroWebFilterConfiguration {
         return Collections.singletonList(DefaultFilter.invalidRequest.name());
     }
 
+    protected ShiroFilterConfiguration shiroFilterConfiguration() {
+        return shiroFilterConfiguration != null
+                ? shiroFilterConfiguration
+                : new ShiroFilterConfiguration();
+    }
+
     protected ShiroFilterFactoryBean shiroFilterFactoryBean() {
         ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
 
@@ -64,6 +74,7 @@ public class AbstractShiroWebFilterConfiguration {
         filterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);
 
         filterFactoryBean.setSecurityManager(securityManager);
+        filterFactoryBean.setShiroFilterConfiguration(shiroFilterConfiguration());
         filterFactoryBean.setGlobalFilters(globalFilters());
         filterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition.getFilterChainMap());
         filterFactoryBean.setFilters(filterMap);
diff --git a/web/src/main/java/org/apache/shiro/web/config/ShiroFilterConfiguration.java b/web/src/main/java/org/apache/shiro/web/config/ShiroFilterConfiguration.java
new file mode 100644
index 00000000..39b4ac2b
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/config/ShiroFilterConfiguration.java
@@ -0,0 +1,87 @@
+/*
+ * 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.config;
+
+import org.apache.shiro.SecurityUtils;
+
+/**
+ * Configuration for Shiro's root level servlet filter.
+ *
+ * @since 1.10.0
+ */
+public class ShiroFilterConfiguration {
+
+    private boolean filterOncePerRequest = false;
+
+    private boolean staticSecurityManagerEnabled = false;
+
+    /**
+     * Returns {@code true} if the filter should only execute once per request. If set to {@code false} the filter
+     * will execute each time it is invoked.
+     * @return {@code true} if this filter should only execute once per request.
+     */
+    public boolean isFilterOncePerRequest() {
+        return filterOncePerRequest;
+    }
+
+    /**
+     * Sets whether the filter executes once per request or for every invocation of the filter. It is recommended
+     * to leave this disabled if you are using a {@link javax.servlet.RequestDispatcher RequestDispatcher} to forward
+     * or include request (JSP tags, programmatically, or via a framework).
+     *
+     * @param filterOncePerRequest Whether this filter executes once per request.
+     */
+    public void setFilterOncePerRequest(boolean filterOncePerRequest) {
+        this.filterOncePerRequest = filterOncePerRequest;
+    }
+
+    /**
+     * Returns {@code true} if the constructed {@link SecurityManager SecurityManager} associated with the filter
+     * should be bound to static memory (via
+     * {@code SecurityUtils.}{@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager}),
+     * {@code false} otherwise.
+     * <p/>
+     * The default value is {@code false}.
+     * <p/>
+     *
+     * @return {@code true} if the constructed {@link SecurityManager SecurityManager} associated with the filter should be bound
+     *         to static memory (via {@code SecurityUtils.}{@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager}),
+     *         {@code false} otherwise.
+     * @see <a href="https://issues.apache.org/jira/browse/SHIRO-287">SHIRO-287</a>
+     */
+    public boolean isStaticSecurityManagerEnabled() {
+        return staticSecurityManagerEnabled;
+    }
+
+    /**
+     * Sets if the constructed {@link SecurityManager SecurityManager} associated with the filter should be bound
+     * to static memory (via {@code SecurityUtils.}{@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager}).
+     * <p/>
+     * The default value is {@code false}.
+     *
+     * @param staticSecurityManagerEnabled if the constructed {@link SecurityManager SecurityManager} associated with the filter
+     *                                       should be bound to static memory (via
+     *                                       {@code SecurityUtils.}{@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager}).
+     * @see <a href="https://issues.apache.org/jira/browse/SHIRO-287">SHIRO-287</a>
+     */
+    public ShiroFilterConfiguration setStaticSecurityManagerEnabled(boolean staticSecurityManagerEnabled) {
+        this.staticSecurityManagerEnabled = staticSecurityManagerEnabled;
+        return this;
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/env/DefaultWebEnvironment.java b/web/src/main/java/org/apache/shiro/web/env/DefaultWebEnvironment.java
index d5658ab4..7ce3d870 100644
--- a/web/src/main/java/org/apache/shiro/web/env/DefaultWebEnvironment.java
+++ b/web/src/main/java/org/apache/shiro/web/env/DefaultWebEnvironment.java
@@ -20,6 +20,7 @@ package org.apache.shiro.web.env;
 
 import org.apache.shiro.env.DefaultEnvironment;
 import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.web.config.ShiroFilterConfiguration;
 import org.apache.shiro.web.filter.mgt.FilterChainResolver;
 import org.apache.shiro.web.mgt.WebSecurityManager;
 
@@ -34,9 +35,12 @@ import java.util.Map;
 public class DefaultWebEnvironment extends DefaultEnvironment implements MutableWebEnvironment {
 
     private static final String DEFAULT_FILTER_CHAIN_RESOLVER_NAME = "filterChainResolver";
+    private static final String SHIRO_FILTER_CONFIG_NAME = "shiroFilter";
 
     private ServletContext servletContext;
 
+    private ShiroFilterConfiguration filterConfiguration;
+
     public DefaultWebEnvironment() {
         super();
     }
@@ -84,4 +88,15 @@ public class DefaultWebEnvironment extends DefaultEnvironment implements Mutable
     public void setServletContext(ServletContext servletContext) {
         this.servletContext = servletContext;
     }
+
+
+    @Override
+    public void setShiroFilterConfiguration(ShiroFilterConfiguration filterConfiguration) {
+        setObject(SHIRO_FILTER_CONFIG_NAME, filterConfiguration);
+    }
+
+    @Override
+    public ShiroFilterConfiguration getShiroFilterConfiguration() {
+        return getObject(SHIRO_FILTER_CONFIG_NAME, ShiroFilterConfiguration.class);
+    }
 }
diff --git a/web/src/main/java/org/apache/shiro/web/env/IniWebEnvironment.java b/web/src/main/java/org/apache/shiro/web/env/IniWebEnvironment.java
index 13e34501..201e9099 100644
--- a/web/src/main/java/org/apache/shiro/web/env/IniWebEnvironment.java
+++ b/web/src/main/java/org/apache/shiro/web/env/IniWebEnvironment.java
@@ -24,6 +24,7 @@ import org.apache.shiro.config.IniFactorySupport;
 import org.apache.shiro.io.ResourceUtils;
 import org.apache.shiro.util.*;
 import org.apache.shiro.web.config.IniFilterChainResolverFactory;
+import org.apache.shiro.web.config.ShiroFilterConfiguration;
 import org.apache.shiro.web.config.WebIniSecurityManagerFactory;
 import org.apache.shiro.web.filter.mgt.FilterChainResolver;
 import org.apache.shiro.web.mgt.WebSecurityManager;
@@ -46,6 +47,7 @@ public class IniWebEnvironment extends ResourceBasedWebEnvironment implements In
 
     public static final String DEFAULT_WEB_INI_RESOURCE_PATH = "/WEB-INF/shiro.ini";
     public static final String FILTER_CHAIN_RESOLVER_NAME = "filterChainResolver";
+    public static final String SHIRO_FILTER_CONFIG_NAME = "shiroFilter";
 
     private static final Logger log = LoggerFactory.getLogger(IniWebEnvironment.class);
 
@@ -119,6 +121,9 @@ public class IniWebEnvironment extends ResourceBasedWebEnvironment implements In
         WebSecurityManager securityManager = createWebSecurityManager();
         setWebSecurityManager(securityManager);
 
+        ShiroFilterConfiguration filterConfiguration = createFilterConfiguration();
+        setShiroFilterConfiguration(filterConfiguration);
+
         FilterChainResolver resolver = createFilterChainResolver();
         if (resolver != null) {
             setFilterChainResolver(resolver);
@@ -252,6 +257,11 @@ public class IniWebEnvironment extends ResourceBasedWebEnvironment implements In
         return ini;
     }
 
+
+    protected ShiroFilterConfiguration createFilterConfiguration() {
+        return (ShiroFilterConfiguration) this.objects.get(SHIRO_FILTER_CONFIG_NAME);
+    }
+
     protected FilterChainResolver createFilterChainResolver() {
 
         FilterChainResolver resolver = null;
@@ -393,6 +403,7 @@ public class IniWebEnvironment extends ResourceBasedWebEnvironment implements In
     protected Map<String, Object> getDefaults() {
         Map<String, Object> defaults = new HashMap<String, Object>();
         defaults.put(FILTER_CHAIN_RESOLVER_NAME, new IniFilterChainResolverFactory());
+        defaults.put(SHIRO_FILTER_CONFIG_NAME, new ShiroFilterConfiguration());
         return defaults;
     }
 
diff --git a/web/src/main/java/org/apache/shiro/web/env/MutableWebEnvironment.java b/web/src/main/java/org/apache/shiro/web/env/MutableWebEnvironment.java
index e071e31d..1c2d3786 100644
--- a/web/src/main/java/org/apache/shiro/web/env/MutableWebEnvironment.java
+++ b/web/src/main/java/org/apache/shiro/web/env/MutableWebEnvironment.java
@@ -18,6 +18,7 @@
  */
 package org.apache.shiro.web.env;
 
+import org.apache.shiro.web.config.ShiroFilterConfiguration;
 import org.apache.shiro.web.filter.mgt.FilterChainResolver;
 import org.apache.shiro.web.mgt.WebSecurityManager;
 
@@ -54,4 +55,11 @@ public interface MutableWebEnvironment extends WebEnvironment {
      * @param webSecurityManager the {@code WebEnvironment}'s {@link WebSecurityManager}.
      */
     void setWebSecurityManager(WebSecurityManager webSecurityManager);
+
+    /**
+     * Sets the {@code WebEnvironment}'s {@link ShiroFilterConfiguration}.
+     *
+     * @param filterConfiguration the {@code WebEnvironment}'s {@link ShiroFilterConfiguration}.
+     */
+    void setShiroFilterConfiguration(ShiroFilterConfiguration filterConfiguration);
 }
diff --git a/web/src/main/java/org/apache/shiro/web/env/WebEnvironment.java b/web/src/main/java/org/apache/shiro/web/env/WebEnvironment.java
index ea7693da..c1c39bff 100644
--- a/web/src/main/java/org/apache/shiro/web/env/WebEnvironment.java
+++ b/web/src/main/java/org/apache/shiro/web/env/WebEnvironment.java
@@ -19,6 +19,7 @@
 package org.apache.shiro.web.env;
 
 import org.apache.shiro.env.Environment;
+import org.apache.shiro.web.config.ShiroFilterConfiguration;
 import org.apache.shiro.web.filter.mgt.FilterChainResolver;
 import org.apache.shiro.web.mgt.WebSecurityManager;
 
@@ -54,4 +55,14 @@ public interface WebEnvironment extends Environment {
      * @return the web application's security manager instance.
      */
     WebSecurityManager getWebSecurityManager();
+
+
+    /**
+     * Returns the configuration object used to configure the ShiroFilter.
+     *
+     * @return the configuration object used to configure the ShiroFilter.
+     */
+    default ShiroFilterConfiguration getShiroFilterConfiguration() {
+        return new ShiroFilterConfiguration();
+    }
 }
diff --git a/web/src/main/java/org/apache/shiro/web/servlet/AbstractShiroFilter.java b/web/src/main/java/org/apache/shiro/web/servlet/AbstractShiroFilter.java
index 7e2ed55b..93f16b4b 100644
--- a/web/src/main/java/org/apache/shiro/web/servlet/AbstractShiroFilter.java
+++ b/web/src/main/java/org/apache/shiro/web/servlet/AbstractShiroFilter.java
@@ -22,6 +22,7 @@ import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.session.Session;
 import org.apache.shiro.subject.ExecutionException;
 import org.apache.shiro.subject.Subject;
+import org.apache.shiro.web.config.ShiroFilterConfiguration;
 import org.apache.shiro.web.filter.mgt.FilterChainResolver;
 import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
 import org.apache.shiro.web.mgt.WebSecurityManager;
@@ -110,6 +111,13 @@ public abstract class AbstractShiroFilter extends OncePerRequestFilter {
         this.filterChainResolver = filterChainResolver;
     }
 
+    public void setShiroFilterConfiguration(ShiroFilterConfiguration config) {
+        this.setFilterOncePerRequest(config.isFilterOncePerRequest());
+
+        // this property could have already been set with a servlet config param
+        this.setStaticSecurityManagerEnabled(config.isStaticSecurityManagerEnabled() || isStaticSecurityManagerEnabled());
+    }
+
     /**
      * Returns {@code true} if the constructed {@link #getSecurityManager() securityManager} reference should be bound
      * to static memory (via
diff --git a/web/src/main/java/org/apache/shiro/web/servlet/OncePerRequestFilter.java b/web/src/main/java/org/apache/shiro/web/servlet/OncePerRequestFilter.java
index 2d0b380c..523919f2 100644
--- a/web/src/main/java/org/apache/shiro/web/servlet/OncePerRequestFilter.java
+++ b/web/src/main/java/org/apache/shiro/web/servlet/OncePerRequestFilter.java
@@ -37,7 +37,7 @@ import java.io.IOException;
  * 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)
+ * {@link #isEnabled()} property to allow explicit control 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.
@@ -65,6 +65,13 @@ public abstract class OncePerRequestFilter extends NameableFilter {
      */
     private boolean enabled = true; //most filters wish to execute when configured, so default to true
 
+    /**
+     * Determines if the filter's once per request functionality is enabled, defaults to false. It is recommended
+     * to leave this disabled if you are using a {@link javax.servlet.RequestDispatcher RequestDispatcher} to forward
+     * or include request (JSP tags, programmatically, or via a framework).
+     */
+    private boolean filterOncePerRequest = false;
+
     /**
      * 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
@@ -95,6 +102,28 @@ public abstract class OncePerRequestFilter extends NameableFilter {
         this.enabled = enabled;
     }
 
+    /**
+     * Returns {@code true} if this filter should only execute once per request. If set to {@code false} this filter
+     * will execute each time it is invoked.
+     * @return {@code true} if this filter should only execute once per request.
+     * @since 1.10
+     */
+    public boolean isFilterOncePerRequest() {
+        return filterOncePerRequest;
+    }
+
+    /**
+     * Sets whether this filter executes once per request or for every invocation of the filter. It is recommended
+     * to leave this disabled if you are using a {@link javax.servlet.RequestDispatcher RequestDispatcher} to forward
+     * or include request (JSP tags, programmatically, or via a framework). 
+     *
+     * @param filterOncePerRequest Whether this filter executes once per request.
+     * @since 1.10
+     */
+    public void setFilterOncePerRequest(boolean filterOncePerRequest) {
+        this.filterOncePerRequest = filterOncePerRequest;
+    }
+
     /**
      * This {@code doFilter} implementation stores a request attribute for
      * "already filtered", proceeding without filtering again if the
@@ -107,7 +136,7 @@ public abstract class OncePerRequestFilter extends NameableFilter {
     public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
             throws ServletException, IOException {
         String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
-        if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
+        if ( request.getAttribute(alreadyFilteredAttributeName) != null && filterOncePerRequest) {
             log.trace("Filter '{}' already executed.  Proceeding without invoking this filter.", getName());
             filterChain.doFilter(request, response);
         } else //noinspection deprecation
diff --git a/web/src/main/java/org/apache/shiro/web/servlet/ShiroFilter.java b/web/src/main/java/org/apache/shiro/web/servlet/ShiroFilter.java
index 1c1a34fc..71376c0e 100644
--- a/web/src/main/java/org/apache/shiro/web/servlet/ShiroFilter.java
+++ b/web/src/main/java/org/apache/shiro/web/servlet/ShiroFilter.java
@@ -70,8 +70,10 @@ public class ShiroFilter extends AbstractShiroFilter {
      */
     @Override
     public void init() throws Exception {
+
         WebEnvironment env = WebUtils.getRequiredWebEnvironment(getServletContext());
 
+        setShiroFilterConfiguration(env.getShiroFilterConfiguration());
         setSecurityManager(env.getWebSecurityManager());
 
         FilterChainResolver resolver = env.getFilterChainResolver();
diff --git a/web/src/test/groovy/org/apache/shiro/web/env/IniWebEnvironmentTest.groovy b/web/src/test/groovy/org/apache/shiro/web/env/IniWebEnvironmentTest.groovy
index cee3cfdf..dfbc6247 100644
--- a/web/src/test/groovy/org/apache/shiro/web/env/IniWebEnvironmentTest.groovy
+++ b/web/src/test/groovy/org/apache/shiro/web/env/IniWebEnvironmentTest.groovy
@@ -52,8 +52,8 @@ class IniWebEnvironmentTest {
         env.init()
 
         assertNotNull env.objects
-        //asserts that the objects size = securityManager (1) + the event bus (1) + filterChainResolverFactory (1) + num custom objects + num default filters
-        def expectedSize = 4 + DefaultFilter.values().length
+        //asserts that the objects size = securityManager (1) + the event bus (1) + filterChainResolverFactory (1) + filterConfig (1) + num custom objects + num default filters
+        def expectedSize = 5 + DefaultFilter.values().length
         assertEquals expectedSize, env.objects.size()
         assertNotNull env.objects['securityManager']
         assertNotNull env.objects['compositeBean']
@@ -84,10 +84,11 @@ class IniWebEnvironmentTest {
         env.init()
 
         assertNotNull env.objects
-        //asserts that the objects size = securityManager (1) + the event bus (1) + filterChainResolverFactory (1) + num custom objects + num default filters
-        def expectedSize = 5 + DefaultFilter.values().length
+        //asserts that the objects size = securityManager (1) + the event bus (1) + filterChainResolverFactory (1) + shiroFilter (1) + num custom objects + num default filters
+        def expectedSize = 6 + DefaultFilter.values().length
         assertEquals expectedSize, env.objects.size()
         assertNotNull env.objects['securityManager']
+        assertNotNull env.objects['shiroFilter']
 
         def compositeBean = (CompositeBean) env.objects['compositeBean']
         def simpleBean = (SimpleBean) env.objects['simpleBean']
diff --git a/web/src/test/groovy/org/apache/shiro/web/env/MockWebEnvironment.groovy b/web/src/test/groovy/org/apache/shiro/web/env/MockWebEnvironment.groovy
index 0b41e042..2bb9a297 100644
--- a/web/src/test/groovy/org/apache/shiro/web/env/MockWebEnvironment.groovy
+++ b/web/src/test/groovy/org/apache/shiro/web/env/MockWebEnvironment.groovy
@@ -19,6 +19,7 @@
 package org.apache.shiro.web.env
 
 import org.apache.shiro.mgt.SecurityManager
+import org.apache.shiro.web.config.ShiroFilterConfiguration
 import org.apache.shiro.web.filter.mgt.FilterChainResolver
 import org.apache.shiro.web.mgt.WebSecurityManager
 
@@ -44,6 +45,11 @@ class MockWebEnvironment implements MutableWebEnvironment {
 
     }
 
+    @Override
+    void setShiroFilterConfiguration(ShiroFilterConfiguration filterConfiguration) {
+
+    }
+
     @Override
     FilterChainResolver getFilterChainResolver() {
         return null
diff --git a/web/src/test/groovy/org/apache/shiro/web/servlet/ShiroFilterTest.groovy b/web/src/test/groovy/org/apache/shiro/web/servlet/ShiroFilterTest.groovy
index 4b04e17d..007e70b3 100644
--- a/web/src/test/groovy/org/apache/shiro/web/servlet/ShiroFilterTest.groovy
+++ b/web/src/test/groovy/org/apache/shiro/web/servlet/ShiroFilterTest.groovy
@@ -18,6 +18,8 @@
  */
 package org.apache.shiro.web.servlet
 
+import org.apache.shiro.web.config.ShiroFilterConfiguration
+
 import javax.servlet.FilterConfig
 import javax.servlet.ServletContext
 import org.apache.shiro.web.env.EnvironmentLoader
@@ -39,6 +41,7 @@ class ShiroFilterTest {
 
         def filterConfig = createStrictMock(FilterConfig)
         def servletContext = createStrictMock(ServletContext)
+        def shiroFilterConfig = createStrictMock(ShiroFilterConfiguration)
         def webEnvironment = createStrictMock(WebEnvironment)
         def webSecurityManager = createStrictMock(WebSecurityManager)
         def filterChainResolver = createStrictMock(FilterChainResolver)
@@ -46,10 +49,13 @@ class ShiroFilterTest {
         expect(filterConfig.servletContext).andReturn(servletContext).anyTimes()
         expect(filterConfig.getInitParameter(eq(AbstractShiroFilter.STATIC_INIT_PARAM_NAME))).andReturn null
         expect(servletContext.getAttribute(eq(EnvironmentLoader.ENVIRONMENT_ATTRIBUTE_KEY))).andReturn webEnvironment
+        expect(shiroFilterConfig.filterOncePerRequest).andReturn true
+        expect(shiroFilterConfig.staticSecurityManagerEnabled).andReturn false
+        expect(webEnvironment.shiroFilterConfiguration).andReturn shiroFilterConfig
         expect(webEnvironment.webSecurityManager).andReturn webSecurityManager
         expect(webEnvironment.filterChainResolver).andReturn filterChainResolver
 
-        replay filterConfig, servletContext, webEnvironment, webSecurityManager, filterChainResolver
+        replay filterConfig, servletContext, webEnvironment, webSecurityManager, filterChainResolver, shiroFilterConfig
 
         ShiroFilter filter = new ShiroFilter()
 
@@ -57,9 +63,67 @@ class ShiroFilterTest {
 
         assertSame filter.securityManager, webSecurityManager
         assertSame filter.filterChainResolver, filterChainResolver
+        assertTrue(filter.isFilterOncePerRequest())
+        assertFalse(filter.isStaticSecurityManagerEnabled())
+
+        verify filterConfig, servletContext, webEnvironment, webSecurityManager, filterChainResolver, shiroFilterConfig
+    }
+
+    @Test
+    void configStaticSecManager_initParm() {
+
+        def filterConfig = createStrictMock(FilterConfig)
+        def servletContext = createStrictMock(ServletContext)
+        def shiroFilterConfig = createStrictMock(ShiroFilterConfiguration)
+        def webEnvironment = createStrictMock(WebEnvironment)
+        def webSecurityManager = createStrictMock(WebSecurityManager)
+        def filterChainResolver = createStrictMock(FilterChainResolver)
+
+        expect(filterConfig.servletContext).andReturn(servletContext).anyTimes()
+        expect(filterConfig.getInitParameter(eq(AbstractShiroFilter.STATIC_INIT_PARAM_NAME))).andReturn "true"
+        expect(servletContext.getAttribute(eq(EnvironmentLoader.ENVIRONMENT_ATTRIBUTE_KEY))).andReturn webEnvironment
+        expect(shiroFilterConfig.filterOncePerRequest).andReturn false
+        expect(shiroFilterConfig.staticSecurityManagerEnabled).andReturn false
+        expect(webEnvironment.shiroFilterConfiguration).andReturn shiroFilterConfig
+        expect(webEnvironment.webSecurityManager).andReturn webSecurityManager
+        expect(webEnvironment.filterChainResolver).andReturn filterChainResolver
+
+        replay filterConfig, servletContext, webEnvironment, webSecurityManager, filterChainResolver, shiroFilterConfig
 
-        verify filterConfig, servletContext, webEnvironment, webSecurityManager, filterChainResolver
+        ShiroFilter filter = new ShiroFilter()
+
+        filter.init(filterConfig)
 
+        assertTrue(filter.isStaticSecurityManagerEnabled())
+        verify filterConfig, servletContext, webEnvironment, webSecurityManager, filterChainResolver, shiroFilterConfig
     }
 
+    @Test
+    void configStaticSecManager_config() {
+
+        def filterConfig = createStrictMock(FilterConfig)
+        def servletContext = createStrictMock(ServletContext)
+        def shiroFilterConfig = createStrictMock(ShiroFilterConfiguration)
+        def webEnvironment = createStrictMock(WebEnvironment)
+        def webSecurityManager = createStrictMock(WebSecurityManager)
+        def filterChainResolver = createStrictMock(FilterChainResolver)
+
+        expect(filterConfig.servletContext).andReturn(servletContext).anyTimes()
+        expect(filterConfig.getInitParameter(eq(AbstractShiroFilter.STATIC_INIT_PARAM_NAME))).andReturn null
+        expect(servletContext.getAttribute(eq(EnvironmentLoader.ENVIRONMENT_ATTRIBUTE_KEY))).andReturn webEnvironment
+        expect(shiroFilterConfig.filterOncePerRequest).andReturn false
+        expect(shiroFilterConfig.staticSecurityManagerEnabled).andReturn true
+        expect(webEnvironment.shiroFilterConfiguration).andReturn shiroFilterConfig
+        expect(webEnvironment.webSecurityManager).andReturn webSecurityManager
+        expect(webEnvironment.filterChainResolver).andReturn filterChainResolver
+
+        replay filterConfig, servletContext, webEnvironment, webSecurityManager, filterChainResolver, shiroFilterConfig
+
+        ShiroFilter filter = new ShiroFilter()
+
+        filter.init(filterConfig)
+
+        assertTrue(filter.isStaticSecurityManagerEnabled())
+        verify filterConfig, servletContext, webEnvironment, webSecurityManager, filterChainResolver, shiroFilterConfig
+    }
 }
diff --git a/web/src/test/java/org/apache/shiro/web/env/WebEnvironmentStub.java b/web/src/test/java/org/apache/shiro/web/env/WebEnvironmentStub.java
index ea050a0f..7dc7f7ca 100644
--- a/web/src/test/java/org/apache/shiro/web/env/WebEnvironmentStub.java
+++ b/web/src/test/java/org/apache/shiro/web/env/WebEnvironmentStub.java
@@ -19,6 +19,7 @@
 package org.apache.shiro.web.env;
 
 import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.web.config.ShiroFilterConfiguration;
 import org.apache.shiro.web.filter.mgt.FilterChainResolver;
 import org.apache.shiro.web.mgt.WebSecurityManager;
 
@@ -32,6 +33,8 @@ public class WebEnvironmentStub implements WebEnvironment, MutableWebEnvironment
 
     private ServletContext servletContext;
 
+    private ShiroFilterConfiguration filterConfiguration;
+
 
     @Override
     public FilterChainResolver getFilterChainResolver() {
@@ -67,4 +70,14 @@ public class WebEnvironmentStub implements WebEnvironment, MutableWebEnvironment
     public SecurityManager getSecurityManager() {
         return getWebSecurityManager();
     }
+
+    @Override
+    public void setShiroFilterConfiguration(ShiroFilterConfiguration filterConfiguration) {
+        this.filterConfiguration = filterConfiguration;
+    }
+
+    @Override
+    public ShiroFilterConfiguration getShiroFilterConfiguration() {
+        return filterConfiguration;
+    }
 }
diff --git a/web/src/test/java/org/apache/shiro/web/servlet/OncePerRequestFilterTest.java b/web/src/test/java/org/apache/shiro/web/servlet/OncePerRequestFilterTest.java
index 8fdc0341..9d9c7d02 100644
--- a/web/src/test/java/org/apache/shiro/web/servlet/OncePerRequestFilterTest.java
+++ b/web/src/test/java/org/apache/shiro/web/servlet/OncePerRequestFilterTest.java
@@ -28,8 +28,7 @@ import javax.servlet.ServletResponse;
 import java.io.IOException;
 
 import static org.easymock.EasyMock.*;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
 
 /**
  * Unit tests for the {@link OncePerRequestFilter} implementation.
@@ -38,35 +37,24 @@ import static org.junit.Assert.assertTrue;
  */
 public class OncePerRequestFilterTest {
 
-    private static final boolean[] FILTERED = new boolean[1];
     private static final String NAME = "oncePerRequestFilter";
     private static final String ATTR_NAME = NAME + OncePerRequestFilter.ALREADY_FILTERED_SUFFIX;
 
-    private OncePerRequestFilter filter;
+    private CountingOncePerRequestFilter filter;
     private FilterChain chain;
     private ServletRequest request;
     private ServletResponse response;
 
     @Before
     public void setUp() {
-        FILTERED[0] = false;
         filter = createTestInstance();
         chain = createNiceMock(FilterChain.class);
         request = createNiceMock(ServletRequest.class);
         response = createNiceMock(ServletResponse.class);
     }
 
-    private OncePerRequestFilter createTestInstance() {
-        OncePerRequestFilter filter = new OncePerRequestFilter() {
-            @Override
-            protected void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
-                    throws ServletException, IOException {
-                FILTERED[0] = true;
-            }
-        };
-        filter.setName(NAME);
-
-        return filter;
+    private CountingOncePerRequestFilter createTestInstance() {
+        return new CountingOncePerRequestFilter();
     }
 
     /**
@@ -81,7 +69,7 @@ public class OncePerRequestFilterTest {
         filter.doFilter(request, response, chain);
 
         verify(request);
-        assertTrue("Filter should have executed", FILTERED[0]);
+        assertEquals("Filter should have executed", 1, filter.filterCount);
     }
 
     /**
@@ -98,7 +86,35 @@ public class OncePerRequestFilterTest {
         filter.doFilter(request, response, chain);
 
         verify(request);
-        assertFalse("Filter should NOT have executed", FILTERED[0]);
+        assertEquals("Filter should NOT have executed", 0, filter.filterCount);
+    }
+
+    @Test
+    public void testFilterOncePerRequest() throws IOException, ServletException {
+        filter.setFilterOncePerRequest(false);
+
+        expect(request.getAttribute(ATTR_NAME)).andReturn(null).andReturn(true);
+        replay(request);
+
+        filter.doFilter(request, response, chain);
+        filter.doFilter(request, response, chain);
+
+        verify(request);
+        assertEquals("Filter should have executed twice", 2, filter.filterCount);
+    }
+
+    static class CountingOncePerRequestFilter extends OncePerRequestFilter {
+
+        private int filterCount = 0;
+
+        public CountingOncePerRequestFilter() {
+            this.setName(NAME);
+        }
+
+        @Override
+        protected void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) {
+            filterCount++;
+        }
     }
 
 }