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++; + } } }