diff --git a/pom.xml b/pom.xml
index 494418bbee..5be23a56ba 100644
--- a/pom.xml
+++ b/pom.xml
@@ -419,6 +419,7 @@
true
true
true
+ ${root.dir}/src/japicmp/postAnalysisScript.groovy
diff --git a/samples/servlet-plugin/pom.xml b/samples/servlet-plugin/pom.xml
index 061a680677..b2315f9d08 100644
--- a/samples/servlet-plugin/pom.xml
+++ b/samples/servlet-plugin/pom.xml
@@ -106,13 +106,8 @@
runtime
- org.apache.logging.log4j
- log4j-slf4j-impl
- runtime
-
-
- org.apache.logging.log4j
- log4j-core
+ org.slf4j
+ slf4j-simple
runtime
diff --git a/src/japicmp/postAnalysisScript.groovy b/src/japicmp/postAnalysisScript.groovy
new file mode 100644
index 0000000000..522e239329
--- /dev/null
+++ b/src/japicmp/postAnalysisScript.groovy
@@ -0,0 +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.
+ */
+
+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 e58449f1a3..ae99b2e3ba 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 @@
*/
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 591d1c6599..a169915482 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 @@
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 @@ public WebSecurityManager getWebSecurityManager() {
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 dbc1017ef0..1e01a113cc 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 void ensureInjectable() {
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 6de5a751cf..c24359a4f4 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.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 static class MyDefaultWebSessionManager extends DefaultWebSessionManager
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 77fada3fec..f5189f2e3d 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 void testConstructor() {
WebSecurityManager securityManager = createMock(WebSecurityManager.class);
FilterChainResolver filterChainResolver = createMock(FilterChainResolver.class);
ServletContext servletContext = createMock(ServletContext.class);
+ ShiroFilterConfiguration filterConfiguration = createMock(ShiroFilterConfiguration.class);
Capture 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 ddc9f865ab..45f3e36390 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.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.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,
private AbstractShiroFilter instance;
+ private ShiroFilterConfiguration filterConfiguration;
+
public ShiroFilterFactoryBean() {
this.filters = new LinkedHashMap();
this.globalFilters = new ArrayList<>();
this.globalFilters.add(DefaultFilter.invalidRequest.name());
this.filterChainDefinitionMap = new LinkedHashMap(); //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 void setSecurityManager(SecurityManager securityManager) {
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 @@ protected AbstractShiroFilter createInstance() throws Exception {
//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 @@ private void applyGlobalPropertiesIfNecessary(Filter filter) {
applyLoginUrlIfNecessary(filter);
applySuccessUrlIfNecessary(filter);
applyUnauthorizedUrlIfNecessary(filter);
+
+ if (filter instanceof OncePerRequestFilter) {
+ ((OncePerRequestFilter) filter).setFilterOncePerRequest(filterConfiguration.isFilterOncePerRequest());
+ }
}
/**
@@ -549,12 +576,13 @@ public Object postProcessAfterInitialization(Object bean, String beanName) throw
*/
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 685d63dfbc..4547c3e290 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 @@
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 filterMap;
@@ -56,6 +60,12 @@ protected List globalFilters() {
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 @@ protected ShiroFilterFactoryBean shiroFilterFactoryBean() {
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 0000000000..39b4ac2b76
--- /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.
+ *
+ * The default value is {@code false}.
+ *
+ *
+ * @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 SHIRO-287
+ */
+ 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}).
+ *
+ * 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 SHIRO-287
+ */
+ 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 d5658ab493..7ce3d870e6 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 @@
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 @@
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 ServletContext getServletContext() {
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 13e3450164..201e9099dc 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.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 @@ protected void configure() {
WebSecurityManager securityManager = createWebSecurityManager();
setWebSecurityManager(securityManager);
+ ShiroFilterConfiguration filterConfiguration = createFilterConfiguration();
+ setShiroFilterConfiguration(filterConfiguration);
+
FilterChainResolver resolver = createFilterChainResolver();
if (resolver != null) {
setFilterChainResolver(resolver);
@@ -252,6 +257,11 @@ protected Ini createIni(String configLocation, boolean required) throws Configur
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 void setIni(Ini ini) {
protected Map getDefaults() {
Map defaults = new HashMap();
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 e071e31d9d..1c2d3786f4 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 ea7693da40..c1c39bffac 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 7e2ed55ba6..93f16b4b23 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.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 void setFilterChainResolver(FilterChainResolver filterChainResolver) {
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 2d0b380c8c..523919f26c 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 @@
* is based on the configured name of the concrete filter instance.
* Controlling filter execution
* 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.
*
* NOTE 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 generally* execute for any request,
* {@code false} if it should let the request/response pass through immediately to the next
@@ -95,6 +102,28 @@ public void setEnabled(boolean enabled) {
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 void setEnabled(boolean enabled) {
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 1c1a34fc10..71376c0efd 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 cee3cfdf7a..dfbc62474b 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 0b41e042fd..2bb9a297d8 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 4b04e17d5c..007e70b3be 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 ea050a0ffd..7dc7f7ca4a 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 void setWebSecurityManager(WebSecurityManager webSecurityManager) {
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 8fdc034166..9d9c7d02fb 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 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 @@
*/
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 void testEnabled() throws IOException, ServletException {
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 void testDisabled() throws IOException, ServletException {
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++;
+ }
}
}