Add a feature to allow for global filters

Adds new filter to block invalid requests
diff --git a/support/guice/src/main/java/org/apache/shiro/guice/web/ShiroWebModule.java b/support/guice/src/main/java/org/apache/shiro/guice/web/ShiroWebModule.java
index df95665..73ffc7b 100644
--- a/support/guice/src/main/java/org/apache/shiro/guice/web/ShiroWebModule.java
+++ b/support/guice/src/main/java/org/apache/shiro/guice/web/ShiroWebModule.java
@@ -30,6 +30,7 @@
 import org.apache.shiro.session.mgt.SessionManager;

 import org.apache.shiro.util.StringUtils;

 import org.apache.shiro.web.env.WebEnvironment;

+import org.apache.shiro.web.filter.InvalidRequestFilter;

 import org.apache.shiro.web.filter.PathMatchingFilter;

 import org.apache.shiro.web.filter.authc.AnonymousFilter;

 import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;

@@ -87,7 +88,8 @@
     public static final Key<SslFilter> SSL = Key.get(SslFilter.class);

     @SuppressWarnings({"UnusedDeclaration"})

     public static final Key<UserFilter> USER = Key.get(UserFilter.class);

-

+    @SuppressWarnings({"UnusedDeclaration"})

+    public static final Key<InvalidRequestFilter> INVALID_REQUEST = Key.get(InvalidRequestFilter.class);

 

     static final String NAME = "SHIRO";

 

@@ -124,6 +126,10 @@
         };

     }

 

+    public List<FilterConfig<? extends Filter>> globalFilters() {

+        return Collections.singletonList(filterConfig(INVALID_REQUEST));

+    }

+

     @Override

     protected final void configureShiro() {

         bindBeanType(TypeLiteral.get(ServletContext.class), Key.get(ServletContext.class, Names.named(NAME)));

@@ -135,6 +141,12 @@
 

         this.configureShiroWeb();

 

+        // add default matching route if not already set

+        if (!filterChains.containsKey("/**")) {

+            // no config, this will add only the global filters

+            this.addFilterChain("/**", new FilterConfig[0]);

+        }

+

         bind(FilterChainResolver.class).toProvider(new FilterChainResolverProvider(setupFilterChainConfigs()));

     }

 

@@ -153,8 +165,15 @@
             // collect the keys used for this path

             List<Key<? extends Filter>> keysForPath = new ArrayList<Key<? extends Filter>>();

 

-            for (int i = 0; i < filterChain.getValue().length; i++) {

-                FilterConfig<? extends Filter> filterConfig = filterChain.getValue()[i];

+            List<FilterConfig<? extends Filter>> globalFilters = this.globalFilters();

+            FilterConfig<? extends Filter>[] pathFilters = filterChain.getValue();

+

+            // merge the global filters and the path specific filters

+            List<FilterConfig<? extends Filter>> filterConfigs = new ArrayList<>(globalFilters.size() + pathFilters.length);

+            filterConfigs.addAll(globalFilters);

+            filterConfigs.addAll(Arrays.asList(pathFilters));

+

+            for (FilterConfig<? extends Filter> filterConfig : filterConfigs) {

 

                 Key<? extends Filter> key = filterConfig.getKey();

                 String config = filterConfig.getConfigValue();

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 9931d01..6de5a75 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
@@ -24,6 +24,7 @@
 import com.google.inject.Key;

 import com.google.inject.Provides;

 import com.google.inject.binder.AnnotatedBindingBuilder;

+import com.google.inject.name.Names;

 import org.apache.shiro.guice.ShiroModuleTest;

 import org.apache.shiro.env.Environment;

 import org.apache.shiro.mgt.SecurityManager;

@@ -31,6 +32,7 @@
 import org.apache.shiro.session.mgt.SessionManager;

 import org.apache.shiro.web.env.EnvironmentLoader;

 import org.apache.shiro.web.env.WebEnvironment;

+import org.apache.shiro.web.filter.InvalidRequestFilter;

 import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;

 import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;

 import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;

@@ -55,7 +57,9 @@
 import javax.servlet.http.HttpServletRequest;

 import java.io.IOException;

 import java.util.Collection;

+import java.util.Collections;

 import java.util.Iterator;

+import java.util.List;

 

 import static org.easymock.EasyMock.*;

 import static org.junit.Assert.*;

@@ -212,29 +216,38 @@
         FilterChain filterChain = simpleFilterChainResolver.getChain(request, null, null);

         assertThat(filterChain, instanceOf(SimpleFilterChain.class));

         Filter nextFilter = getNextFilter((SimpleFilterChain) filterChain);

+        assertThat(nextFilter, instanceOf(InvalidRequestFilter.class));

+        nextFilter = getNextFilter((SimpleFilterChain) filterChain);

         assertThat(nextFilter, instanceOf(FormAuthenticationFilter.class));

 

         // test the /test_custom_filter resource

         filterChain = simpleFilterChainResolver.getChain(request, null, null);

         assertThat(filterChain, instanceOf(SimpleFilterChain.class));

         nextFilter = getNextFilter((SimpleFilterChain) filterChain);

+        assertThat(nextFilter, instanceOf(InvalidRequestFilter.class));

+        nextFilter = getNextFilter((SimpleFilterChain) filterChain);

         assertThat(nextFilter, instanceOf(CustomFilter.class));

 

         // test the /test_authc_basic resource

         filterChain = simpleFilterChainResolver.getChain(request, null, null);

         assertThat(filterChain, instanceOf(SimpleFilterChain.class));

         nextFilter = getNextFilter((SimpleFilterChain) filterChain);

+        assertThat(nextFilter, instanceOf(InvalidRequestFilter.class));

+        nextFilter = getNextFilter((SimpleFilterChain) filterChain);

         assertThat(nextFilter, instanceOf(BasicHttpAuthenticationFilter.class));

 

         // test the /test_perms resource

         filterChain = simpleFilterChainResolver.getChain(request, null, null);

         assertThat(filterChain, instanceOf(SimpleFilterChain.class));

         nextFilter = getNextFilter((SimpleFilterChain) filterChain);

+        assertThat(nextFilter, instanceOf(InvalidRequestFilter.class));

+        nextFilter = getNextFilter((SimpleFilterChain) filterChain);

         assertThat(nextFilter, instanceOf(PermissionsAuthorizationFilter.class));

 

         // test the /multiple_configs resource

         filterChain = simpleFilterChainResolver.getChain(request, null, null);

         assertThat(filterChain, instanceOf(SimpleFilterChain.class));

+        assertThat(getNextFilter((SimpleFilterChain) filterChain), instanceOf(InvalidRequestFilter.class));

         assertThat(getNextFilter((SimpleFilterChain) filterChain), instanceOf(FormAuthenticationFilter.class));

         assertThat(getNextFilter((SimpleFilterChain) filterChain), instanceOf(RolesAuthorizationFilter.class));

         assertThat(getNextFilter((SimpleFilterChain) filterChain), instanceOf(PermissionsAuthorizationFilter.class));

@@ -312,6 +325,146 @@
         verify(servletContext, request);

     }

 

+    @Test

+    public void testDefaultPath() {

+

+        final ShiroModuleTest.MockRealm mockRealm = createMock(ShiroModuleTest.MockRealm.class);

+        ServletContext servletContext = createMock(ServletContext.class);

+        HttpServletRequest request = createMock(HttpServletRequest.class);

+

+        servletContext.setAttribute(eq(EnvironmentLoader.ENVIRONMENT_ATTRIBUTE_KEY), EasyMock.anyObject());

+        expect(request.getAttribute("javax.servlet.include.context_path")).andReturn("").anyTimes();

+        expect(request.getCharacterEncoding()).andReturn("UTF-8").anyTimes();

+        expect(request.getAttribute("javax.servlet.include.path_info")).andReturn(null).anyTimes();

+        expect(request.getPathInfo()).andReturn(null).anyTimes();

+        expect(request.getAttribute("javax.servlet.include.servlet_path")).andReturn("/test/foobar");

+        replay(servletContext, request);

+

+        Injector injector = Guice.createInjector(new ShiroWebModule(servletContext) {

+            @Override

+            protected void configureShiroWeb() {

+                bindRealm().to(ShiroModuleTest.MockRealm.class);

+                expose(FilterChainResolver.class);

+                // no paths configured

+            }

+

+            @Provides

+            public ShiroModuleTest.MockRealm createRealm() {

+                return mockRealm;

+            }

+        });

+

+        FilterChainResolver resolver = injector.getInstance(FilterChainResolver.class);

+        assertThat(resolver, instanceOf(SimpleFilterChainResolver.class));

+        SimpleFilterChainResolver simpleFilterChainResolver = (SimpleFilterChainResolver) resolver;

+

+        // test the /test_authc resource

+        FilterChain filterChain = simpleFilterChainResolver.getChain(request, null, null);

+        assertThat(filterChain, instanceOf(SimpleFilterChain.class));

+

+        assertThat(getNextFilter((SimpleFilterChain) filterChain), instanceOf(InvalidRequestFilter.class));

+        assertThat(getNextFilter((SimpleFilterChain) filterChain), nullValue());

+

+        verify(servletContext, request);

+    }

+

+    @Test

+    public void testDisableGlobalFilters() {

+

+        final ShiroModuleTest.MockRealm mockRealm = createMock(ShiroModuleTest.MockRealm.class);

+        ServletContext servletContext = createMock(ServletContext.class);

+        HttpServletRequest request = createMock(HttpServletRequest.class);

+

+        servletContext.setAttribute(eq(EnvironmentLoader.ENVIRONMENT_ATTRIBUTE_KEY), EasyMock.anyObject());

+        expect(request.getAttribute("javax.servlet.include.context_path")).andReturn("").anyTimes();

+        expect(request.getCharacterEncoding()).andReturn("UTF-8").anyTimes();

+        expect(request.getAttribute("javax.servlet.include.path_info")).andReturn(null).anyTimes();

+        expect(request.getPathInfo()).andReturn(null).anyTimes();

+        expect(request.getAttribute("javax.servlet.include.servlet_path")).andReturn("/test/foobar");

+        replay(servletContext, request);

+

+        Injector injector = Guice.createInjector(new ShiroWebModule(servletContext) {

+            @Override

+            protected void configureShiroWeb() {

+                bindRealm().to(ShiroModuleTest.MockRealm.class);

+                expose(FilterChainResolver.class);

+                this.addFilterChain("/**", filterConfig(AUTHC));

+            }

+

+            @Override

+            public List<FilterConfig<? extends Filter>> globalFilters() {

+                return Collections.emptyList();

+            }

+

+            @Provides

+            public ShiroModuleTest.MockRealm createRealm() {

+                return mockRealm;

+            }

+        });

+

+        FilterChainResolver resolver = injector.getInstance(FilterChainResolver.class);

+        assertThat(resolver, instanceOf(SimpleFilterChainResolver.class));

+        SimpleFilterChainResolver simpleFilterChainResolver = (SimpleFilterChainResolver) resolver;

+

+        // test the /test_authc resource

+        FilterChain filterChain = simpleFilterChainResolver.getChain(request, null, null);

+        assertThat(filterChain, instanceOf(SimpleFilterChain.class));

+

+        assertThat(getNextFilter((SimpleFilterChain) filterChain), instanceOf(FormAuthenticationFilter.class));

+        assertThat(getNextFilter((SimpleFilterChain) filterChain), nullValue());

+

+        verify(servletContext, request);

+    }

+

+    @Test

+    public void testChangeInvalidFilterConfig() {

+

+        final ShiroModuleTest.MockRealm mockRealm = createMock(ShiroModuleTest.MockRealm.class);

+        ServletContext servletContext = createMock(ServletContext.class);

+        HttpServletRequest request = createMock(HttpServletRequest.class);

+

+        servletContext.setAttribute(eq(EnvironmentLoader.ENVIRONMENT_ATTRIBUTE_KEY), EasyMock.anyObject());

+        expect(request.getAttribute("javax.servlet.include.context_path")).andReturn("").anyTimes();

+        expect(request.getCharacterEncoding()).andReturn("UTF-8").anyTimes();

+        expect(request.getAttribute("javax.servlet.include.path_info")).andReturn(null).anyTimes();

+        expect(request.getPathInfo()).andReturn(null).anyTimes();

+        expect(request.getAttribute("javax.servlet.include.servlet_path")).andReturn("/test/foobar");

+        replay(servletContext, request);

+

+        Injector injector = Guice.createInjector(new ShiroWebModule(servletContext) {

+            @Override

+            protected void configureShiroWeb() {

+

+                bindConstant().annotatedWith(Names.named("shiro.blockBackslash")).to(false);

+

+                bindRealm().to(ShiroModuleTest.MockRealm.class);

+                expose(FilterChainResolver.class);

+                this.addFilterChain("/**", filterConfig(AUTHC));

+            }

+

+            @Provides

+            public ShiroModuleTest.MockRealm createRealm() {

+                return mockRealm;

+            }

+        });

+

+        FilterChainResolver resolver = injector.getInstance(FilterChainResolver.class);

+        assertThat(resolver, instanceOf(SimpleFilterChainResolver.class));

+        SimpleFilterChainResolver simpleFilterChainResolver = (SimpleFilterChainResolver) resolver;

+

+        // test the /test_authc resource

+        FilterChain filterChain = simpleFilterChainResolver.getChain(request, null, null);

+        assertThat(filterChain, instanceOf(SimpleFilterChain.class));

+

+        Filter invalidRequestFilter = getNextFilter((SimpleFilterChain) filterChain);

+        assertThat(invalidRequestFilter, instanceOf(InvalidRequestFilter.class));

+        assertFalse("Expected 'blockBackslash' to be false", ((InvalidRequestFilter) invalidRequestFilter).isBlockBackslash());

+        assertThat(getNextFilter((SimpleFilterChain) filterChain), instanceOf(FormAuthenticationFilter.class));

+        assertThat(getNextFilter((SimpleFilterChain) filterChain), nullValue());

+

+        verify(servletContext, request);

+    }

+

     private Filter getNextFilter(SimpleFilterChain filterChain) {

 

         Iterator<? extends Filter> filters = filterChain.getFilters();

diff --git a/support/spring-boot/spring-boot-web-starter/src/main/java/org/apache/shiro/spring/config/web/autoconfigure/ShiroWebFilterConfiguration.java b/support/spring-boot/spring-boot-web-starter/src/main/java/org/apache/shiro/spring/config/web/autoconfigure/ShiroWebFilterConfiguration.java
index 69356d6..9a60a0c 100644
--- a/support/spring-boot/spring-boot-web-starter/src/main/java/org/apache/shiro/spring/config/web/autoconfigure/ShiroWebFilterConfiguration.java
+++ b/support/spring-boot/spring-boot-web-starter/src/main/java/org/apache/shiro/spring/config/web/autoconfigure/ShiroWebFilterConfiguration.java
@@ -28,6 +28,8 @@
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
+import java.util.List;
+
 /**
  * @since 1.4.0
  */
@@ -53,4 +55,10 @@
 
         return filterRegistrationBean;
     }
+
+    @Bean(name = "globalFilters")
+    @ConditionalOnMissingBean
+    protected List<String> globalFilters() {
+        return super.globalFilters();
+    }
 }
diff --git a/support/spring-boot/spring-boot-web-starter/src/test/groovy/org/apache/shiro/spring/boot/autoconfigure/web/ConfiguredGlobalFiltersTest.groovy b/support/spring-boot/spring-boot-web-starter/src/test/groovy/org/apache/shiro/spring/boot/autoconfigure/web/ConfiguredGlobalFiltersTest.groovy
new file mode 100644
index 0000000..34c89a4
--- /dev/null
+++ b/support/spring-boot/spring-boot-web-starter/src/test/groovy/org/apache/shiro/spring/boot/autoconfigure/web/ConfiguredGlobalFiltersTest.groovy
@@ -0,0 +1,104 @@
+/*
+ * 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.spring.boot.autoconfigure.web
+
+import org.apache.shiro.spring.boot.autoconfigure.web.application.ShiroWebAutoConfigurationTestApplication
+import org.apache.shiro.spring.web.ShiroFilterFactoryBean
+import org.apache.shiro.spring.web.config.AbstractShiroWebFilterConfiguration
+import org.apache.shiro.web.filter.InvalidRequestFilter
+import org.apache.shiro.web.filter.authz.PortFilter
+import org.apache.shiro.web.filter.mgt.DefaultFilter
+import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager
+import org.apache.shiro.web.filter.mgt.NamedFilterList
+import org.apache.shiro.web.servlet.AbstractShiroFilter
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.test.context.junit4.SpringRunner
+
+import static org.hamcrest.MatcherAssert.assertThat
+import static org.hamcrest.Matchers.*
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = [ShiroWebAutoConfigurationTestApplication, Config])
+
+class ConfiguredGlobalFiltersTest {
+
+    @Configuration
+    static class Config extends AbstractShiroWebFilterConfiguration {
+
+        @Bean
+        List<String> globalFilters() {
+            return [DefaultFilter.invalidRequest.name(), DefaultFilter.port.name()]
+        }
+
+        @Bean
+        @Override
+        ShiroFilterFactoryBean shiroFilterFactoryBean() {
+            ShiroFilterFactoryBean bean = super.shiroFilterFactoryBean()
+            InvalidRequestFilter invalidRequestFilter = new InvalidRequestFilter()
+            invalidRequestFilter.setBlockBackslash(false)
+            PortFilter portFilter = new PortFilter()
+            portFilter.setPort(9999)
+            bean.getFilters().put("invalidRequest", invalidRequestFilter)
+            bean.getFilters().put("port", portFilter)
+            return bean
+        }
+    }
+
+    @Autowired
+    private AbstractShiroFilter shiroFilter
+
+    @Test
+    void testGlobalFiltersConfigured() {
+        // make sure global chains are configured
+        assertThat shiroFilter.filterChainResolver.filterChainManager, instanceOf(DefaultFilterChainManager)
+        DefaultFilterChainManager filterChainManager = shiroFilter.filterChainResolver.filterChainManager
+
+        // default config set
+        assertThat filterChainManager.globalFilterNames, contains(DefaultFilter.invalidRequest.name(),
+                                                                  DefaultFilter.port.name())
+        // default route configured
+        NamedFilterList allChain = filterChainManager.getChain("/**")
+        assertThat allChain, contains(
+                instanceOf(DefaultFilter.invalidRequest.filterClass),
+                instanceOf(DefaultFilter.port.filterClass))
+
+        InvalidRequestFilter invalidRequest = allChain.get(0)
+        assertThat "Expected invalidRequest.blockBackslash to be false", !invalidRequest.isBlockBackslash()
+        PortFilter portFilter = allChain.get(1) // an ugly line, but we want to make sure that we can override the filters
+        // defined in Shiro's DefaultFilter
+        assertThat portFilter.port, equalTo(9999)
+
+        // configured routes also contain global filters
+        NamedFilterList loginChain = filterChainManager.getChain("/login.html")
+        assertThat loginChain, contains(
+                instanceOf(DefaultFilter.invalidRequest.filterClass),
+                instanceOf(DefaultFilter.port.filterClass),
+                instanceOf(DefaultFilter.authc.filterClass)) // configured in ShiroWebAutoConfigurationTestApplication
+
+        assertThat loginChain.get(0), sameInstance(invalidRequest)
+        assertThat loginChain.get(1), sameInstance(portFilter)
+
+
+    }
+}
diff --git a/support/spring-boot/spring-boot-web-starter/src/test/groovy/org/apache/shiro/spring/boot/autoconfigure/web/DisabledGlobalFiltersTest.groovy b/support/spring-boot/spring-boot-web-starter/src/test/groovy/org/apache/shiro/spring/boot/autoconfigure/web/DisabledGlobalFiltersTest.groovy
new file mode 100644
index 0000000..983b666
--- /dev/null
+++ b/support/spring-boot/spring-boot-web-starter/src/test/groovy/org/apache/shiro/spring/boot/autoconfigure/web/DisabledGlobalFiltersTest.groovy
@@ -0,0 +1,64 @@
+/*
+ * 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.spring.boot.autoconfigure.web;
+
+import org.apache.shiro.spring.boot.autoconfigure.web.application.ShiroWebAutoConfigurationTestApplication
+import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager
+import org.apache.shiro.web.servlet.AbstractShiroFilter
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.test.context.junit4.SpringRunner
+
+import static org.hamcrest.MatcherAssert.assertThat
+import static org.hamcrest.Matchers.equalTo
+import static org.hamcrest.Matchers.instanceOf
+import static org.hamcrest.Matchers.nullValue
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = [ShiroWebAutoConfigurationTestApplication, Config])
+class DisabledGlobalFiltersTest {
+
+    @Configuration
+    static class Config {
+
+        @Bean
+        List<String> globalFilters() {
+            return []
+        }
+    }
+
+    @Autowired
+    private AbstractShiroFilter shiroFilter
+
+    @Test
+    void testGlobalFiltersDisabled() {
+        // make sure global chains are configured
+        assertThat shiroFilter.filterChainResolver.filterChainManager, instanceOf(DefaultFilterChainManager)
+        DefaultFilterChainManager filterChainManager = shiroFilter.filterChainResolver.filterChainManager
+
+        // default config set
+        assertThat filterChainManager.globalFilterNames, equalTo([])
+        // default route configured
+        assertThat filterChainManager.getChain("/**"), nullValue()
+    }
+}
diff --git a/support/spring-boot/spring-boot-web-starter/src/test/groovy/org/apache/shiro/spring/boot/autoconfigure/web/ShiroWebSpringAutoConfigurationTest.groovy b/support/spring-boot/spring-boot-web-starter/src/test/groovy/org/apache/shiro/spring/boot/autoconfigure/web/ShiroWebSpringAutoConfigurationTest.groovy
index e69d052..ad19589 100644
--- a/support/spring-boot/spring-boot-web-starter/src/test/groovy/org/apache/shiro/spring/boot/autoconfigure/web/ShiroWebSpringAutoConfigurationTest.groovy
+++ b/support/spring-boot/spring-boot-web-starter/src/test/groovy/org/apache/shiro/spring/boot/autoconfigure/web/ShiroWebSpringAutoConfigurationTest.groovy
@@ -25,20 +25,26 @@
 import org.apache.shiro.event.EventBus
 import org.apache.shiro.mgt.DefaultSecurityManager
 import org.apache.shiro.mgt.SecurityManager
+import org.apache.shiro.web.filter.mgt.DefaultFilter
+import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager
 import org.apache.shiro.web.mgt.WebSecurityManager
+import org.apache.shiro.web.servlet.AbstractShiroFilter
 import org.junit.Test
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.boot.test.context.SpringBootTest
 import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests
 
-import static org.hamcrest.MatcherAssert.assertThat
+import static org.hamcrest.Matchers.contains
+import static org.hamcrest.Matchers.equalTo
 import static org.hamcrest.Matchers.instanceOf
 import static org.junit.Assert.*
+import static org.hamcrest.MatcherAssert.assertThat
+
 /**
  * @since 1.4.0
  */
 @SpringBootTest(classes = [ShiroWebAutoConfigurationTestApplication])
-public class ShiroWebSpringAutoConfigurationTest extends AbstractJUnit4SpringContextTests {
+class ShiroWebSpringAutoConfigurationTest extends AbstractJUnit4SpringContextTests {
 
     @Autowired
     private SecurityManager securityManager
@@ -52,8 +58,11 @@
     @Autowired
     private SubscribedListener subscribedListener
 
+    @Autowired
+    private AbstractShiroFilter shiroFilter
+
     @Test
-    public void testMinimalConfiguration() {
+    void testMinimalConfiguration() {
 
         // first do a quick check of the injected objects
         assertNotNull securityManager
@@ -61,11 +70,22 @@
 
         assertNotNull eventBusAwareObject
         assertNotNull eventBus
+        assertNotNull shiroFilter
         assertTrue(eventBus.registry.containsKey(subscribedListener))
         assertSame(eventBusAwareObject.getEventBus(), eventBus)
         assertSame(((DefaultSecurityManager)securityManager).getEventBus(), eventBus)
 
+        // make sure global chains are configured
+        assertThat shiroFilter.filterChainResolver.filterChainManager, instanceOf(DefaultFilterChainManager)
+        DefaultFilterChainManager filterChainManager = shiroFilter.filterChainResolver.filterChainManager
+
+        // default config set
+        assertThat filterChainManager.globalFilterNames, equalTo([DefaultFilter.invalidRequest.name()])
+        // default route configured
+        assertThat filterChainManager.getChain("/**"), contains(instanceOf(DefaultFilter.invalidRequest.filterClass))
+        // configured routes also contain global filters
+        assertThat filterChainManager.getChain("/login.html"), contains(
+                instanceOf(DefaultFilter.invalidRequest.filterClass),
+                instanceOf(DefaultFilter.authc.filterClass)) // configured in ShiroWebAutoConfigurationTestApplication
     }
-
-
 }
diff --git a/support/spring-boot/spring-boot-web-starter/src/test/java/org/apache/shiro/spring/boot/autoconfigure/web/application/ShiroWebAutoConfigurationTestApplication.java b/support/spring-boot/spring-boot-web-starter/src/test/java/org/apache/shiro/spring/boot/autoconfigure/web/application/ShiroWebAutoConfigurationTestApplication.java
index c1fdc43..8e778bb 100644
--- a/support/spring-boot/spring-boot-web-starter/src/test/java/org/apache/shiro/spring/boot/autoconfigure/web/application/ShiroWebAutoConfigurationTestApplication.java
+++ b/support/spring-boot/spring-boot-web-starter/src/test/java/org/apache/shiro/spring/boot/autoconfigure/web/application/ShiroWebAutoConfigurationTestApplication.java
@@ -55,7 +55,9 @@
 
     @Bean
     ShiroFilterChainDefinition shiroFilterChainDefinition() {
-        return new DefaultShiroFilterChainDefinition();
+        DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
+        chainDefinition.addPathDefinition("/login.html", "authc");
+        return chainDefinition;
     }
 
     @Bean
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 5f006a1..1065a63 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
@@ -25,8 +25,10 @@
 import org.apache.shiro.util.StringUtils;
 import org.apache.shiro.web.config.IniFilterChainResolverFactory;
 import org.apache.shiro.web.filter.AccessControlFilter;
+import org.apache.shiro.web.filter.InvalidRequestFilter;
 import org.apache.shiro.web.filter.authc.AuthenticationFilter;
 import org.apache.shiro.web.filter.authz.AuthorizationFilter;
+import org.apache.shiro.web.filter.mgt.DefaultFilter;
 import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
 import org.apache.shiro.web.filter.mgt.FilterChainManager;
 import org.apache.shiro.web.filter.mgt.FilterChainResolver;
@@ -41,7 +43,9 @@
 import org.springframework.beans.factory.config.BeanPostProcessor;
 
 import javax.servlet.Filter;
+import java.util.ArrayList;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -121,6 +125,8 @@
 
     private Map<String, Filter> filters;
 
+    private List<String> globalFilters;
+
     private Map<String, String> filterChainDefinitionMap; //urlPathExpression_to_comma-delimited-filter-chain-definition
 
     private String loginUrl;
@@ -131,6 +137,8 @@
 
     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!
     }
 
@@ -332,6 +340,14 @@
     }
 
     /**
+     * Sets the list of filters that will be executed against every request.  Defaults to the {@link InvalidRequestFilter} which will block known invalid request attacks.
+     * @param globalFilters the list of filters to execute before specific path filters.
+     */
+    public void setGlobalFilters(List<String> globalFilters) {
+        this.globalFilters = globalFilters;
+    }
+
+    /**
      * Lazily creates and returns a {@link AbstractShiroFilter} concrete instance via the
      * {@link #createInstance} method.
      *
@@ -388,6 +404,9 @@
             }
         }
 
+        // set the global filters
+        manager.setGlobalFilters(this.globalFilters);
+
         //build up the chains:
         Map<String, String> chains = getFilterChainDefinitionMap();
         if (!CollectionUtils.isEmpty(chains)) {
@@ -398,6 +417,9 @@
             }
         }
 
+        // create the default chain, to match anything the path matching would have missed
+        manager.createDefaultChain("/**"); // TODO this assumes ANT path matching, which might be OK here
+
         return manager;
     }
 
@@ -533,6 +555,7 @@
                 throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
             }
             setSecurityManager(webSecurityManager);
+
             if (resolver != null) {
                 setFilterChainResolver(resolver);
             }
diff --git a/support/spring/src/main/java/org/apache/shiro/spring/web/config/AbstractShiroWebConfiguration.java b/support/spring/src/main/java/org/apache/shiro/spring/web/config/AbstractShiroWebConfiguration.java
index 6e2cad3..414d7b1 100644
--- a/support/spring/src/main/java/org/apache/shiro/spring/web/config/AbstractShiroWebConfiguration.java
+++ b/support/spring/src/main/java/org/apache/shiro/spring/web/config/AbstractShiroWebConfiguration.java
@@ -18,13 +18,10 @@
  */
 package org.apache.shiro.spring.web.config;
 
-import org.apache.shiro.config.Ini;
 import org.apache.shiro.mgt.RememberMeManager;
 import org.apache.shiro.mgt.SessionStorageEvaluator;
 import org.apache.shiro.mgt.SessionsSecurityManager;
 import org.apache.shiro.mgt.SubjectFactory;
-import org.apache.shiro.realm.Realm;
-import org.apache.shiro.realm.text.IniRealm;
 import org.apache.shiro.session.mgt.SessionManager;
 import org.apache.shiro.spring.config.AbstractShiroConfiguration;
 import org.apache.shiro.web.mgt.CookieRememberMeManager;
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 e15d50d..11b961e 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
@@ -18,13 +18,15 @@
  */
 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.filter.mgt.DefaultFilter;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 
 import javax.servlet.Filter;
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -50,6 +52,10 @@
     @Value("#{ @environment['shiro.unauthorizedUrl'] ?: null }")
     protected String unauthorizedUrl;
 
+    protected List<String> globalFilters() {
+        return Collections.singletonList(DefaultFilter.invalidRequest.name());
+    }
+
     protected ShiroFilterFactoryBean shiroFilterFactoryBean() {
         ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
 
@@ -58,6 +64,7 @@
         filterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);
 
         filterFactoryBean.setSecurityManager(securityManager);
+        filterFactoryBean.setGlobalFilters(globalFilters());
         filterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition.getFilterChainMap());
         filterFactoryBean.setFilters(filterMap);
 
diff --git a/support/spring/src/main/java/org/apache/shiro/spring/web/config/ShiroWebFilterConfiguration.java b/support/spring/src/main/java/org/apache/shiro/spring/web/config/ShiroWebFilterConfiguration.java
index 72a4ecb..0ddcab1 100644
--- a/support/spring/src/main/java/org/apache/shiro/spring/web/config/ShiroWebFilterConfiguration.java
+++ b/support/spring/src/main/java/org/apache/shiro/spring/web/config/ShiroWebFilterConfiguration.java
@@ -22,6 +22,8 @@
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
+import java.util.List;
+
 /**
  * @since 1.4.0
  */
@@ -34,4 +36,8 @@
         return super.shiroFilterFactoryBean();
     }
 
+    @Bean(name = "globalFilters")
+    protected List<String> globalFilters() {
+        return super.globalFilters();
+    }
 }
diff --git a/support/spring/src/test/groovy/org/apache/shiro/spring/config/ShiroWebFilterConfigurationTest.groovy b/support/spring/src/test/groovy/org/apache/shiro/spring/config/ShiroWebFilterConfigurationTest.groovy
index 68870dd..bd7e25c 100644
--- a/support/spring/src/test/groovy/org/apache/shiro/spring/config/ShiroWebFilterConfigurationTest.groovy
+++ b/support/spring/src/test/groovy/org/apache/shiro/spring/config/ShiroWebFilterConfigurationTest.groovy
@@ -24,6 +24,7 @@
 import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition
 import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition
 import org.apache.shiro.spring.web.config.ShiroWebFilterConfiguration
+import org.apache.shiro.web.filter.InvalidRequestFilter
 import org.apache.shiro.web.filter.mgt.FilterChainManager
 import org.junit.Test
 import org.springframework.beans.factory.annotation.Autowired
@@ -66,7 +67,7 @@
         // create the filter chain manager
         FilterChainManager filterChainManager = shiroFilterFactoryBean.createFilterChainManager()
         // lookup the chain by name
-        assertThat filterChainManager.getChain("/test-me"), contains(instanceOf(ExpectedTestFilter))
+        assertThat filterChainManager.getChain("/test-me"), contains(instanceOf(InvalidRequestFilter), instanceOf(ExpectedTestFilter))
     }
 
     @Configuration
diff --git a/support/spring/src/test/java/org/apache/shiro/spring/web/ShiroFilterFactoryBeanTest.java b/support/spring/src/test/java/org/apache/shiro/spring/web/ShiroFilterFactoryBeanTest.java
index b2ba586..603d1c4 100644
--- a/support/spring/src/test/java/org/apache/shiro/spring/web/ShiroFilterFactoryBeanTest.java
+++ b/support/spring/src/test/java/org/apache/shiro/spring/web/ShiroFilterFactoryBeanTest.java
@@ -18,6 +18,7 @@
  */
 package org.apache.shiro.spring.web;
 
+import org.apache.shiro.web.filter.InvalidRequestFilter;
 import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
 import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
 import org.apache.shiro.web.filter.mgt.NamedFilterList;
@@ -55,11 +56,12 @@
         DefaultFilterChainManager fcManager = (DefaultFilterChainManager) resolver.getFilterChainManager();
         NamedFilterList chain = fcManager.getChain("/test");
         assertNotNull(chain);
-        assertEquals(chain.size(), 2);
+        assertEquals(chain.size(), 3);
         Filter[] filters = new Filter[chain.size()];
         filters = chain.toArray(filters);
-        assertTrue(filters[0] instanceof DummyFilter);
-        assertTrue(filters[1] instanceof FormAuthenticationFilter);
+        assertTrue(filters[0] instanceof InvalidRequestFilter); // global filter
+        assertTrue(filters[1] instanceof DummyFilter);
+        assertTrue(filters[2] instanceof FormAuthenticationFilter);
     }
 
     /**
diff --git a/web/src/main/java/org/apache/shiro/web/config/IniFilterChainResolverFactory.java b/web/src/main/java/org/apache/shiro/web/config/IniFilterChainResolverFactory.java
index fb9f822..7118164 100644
--- a/web/src/main/java/org/apache/shiro/web/config/IniFilterChainResolverFactory.java
+++ b/web/src/main/java/org/apache/shiro/web/config/IniFilterChainResolverFactory.java
@@ -24,6 +24,7 @@
 import org.apache.shiro.config.ReflectionBuilder;
 import org.apache.shiro.util.CollectionUtils;
 import org.apache.shiro.util.Factory;
+import org.apache.shiro.web.filter.mgt.DefaultFilter;
 import org.apache.shiro.web.filter.mgt.FilterChainManager;
 import org.apache.shiro.web.filter.mgt.FilterChainResolver;
 import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
@@ -32,7 +33,9 @@
 
 import javax.servlet.Filter;
 import javax.servlet.FilterConfig;
+import java.util.Collections;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -49,6 +52,8 @@
 
     private FilterConfig filterConfig;
 
+    private List<String> globalFilters = Collections.singletonList(DefaultFilter.invalidRequest.name());
+
     public IniFilterChainResolverFactory() {
         super();
     }
@@ -70,6 +75,14 @@
         this.filterConfig = filterConfig;
     }
 
+    public List<String> getGlobalFilters() {
+        return globalFilters;
+    }
+
+    public void setGlobalFilters(List<String> globalFilters) {
+        this.globalFilters = globalFilters;
+    }
+
     protected FilterChainResolver createInstance(Ini ini) {
         FilterChainResolver filterChainResolver = createDefaultInstance();
         if (filterChainResolver instanceof PathMatchingFilterChainResolver) {
@@ -121,9 +134,14 @@
         //add the filters to the manager:
         registerFilters(filters, manager);
 
+        manager.setGlobalFilters(getGlobalFilters());
+
         //urls section:
         section = ini.getSection(URLS);
         createChains(section, manager);
+
+        // create the default chain, to match anything the path matching would have missed
+        manager.createDefaultChain("/**"); // TODO this assumes ANT path matching
     }
 
     protected void registerFilters(Map<String, Filter> filters, FilterChainManager manager) {
diff --git a/web/src/main/java/org/apache/shiro/web/filter/InvalidRequestFilter.java b/web/src/main/java/org/apache/shiro/web/filter/InvalidRequestFilter.java
new file mode 100644
index 0000000..3d229e9
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/InvalidRequestFilter.java
@@ -0,0 +1,124 @@
+/*
+ * 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.filter;
+
+import org.apache.shiro.web.util.WebUtils;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A request filter that blocks malicious requests. Invalid request will respond with a 400 response code.
+ * <p>
+ * This filter checks and blocks the request if the following characters are found in the request URI:
+ * <ul>
+ *     <li>Semicolon - can be disabled by setting {@code blockSemicolon = false}</li>
+ *     <li>Backslash - can be disabled by setting {@code blockBackslash = false}</li>
+ *     <li>Non-ASCII characters - can be disabled by setting {@code blockNonAscii = false}, the ability to disable this check will be removed in future version.</li>
+ * </ul>
+ *
+ * @see <a href="https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/firewall/StrictHttpFirewall.html">This class was inspired by Spring Security StrictHttpFirewall</a>
+ * @since 1.6
+ */
+public class InvalidRequestFilter extends AccessControlFilter {
+
+    private static final List<String> SEMICOLON = Collections.unmodifiableList(Arrays.asList(";", "%3b", "%3B"));
+
+    private static final List<String> BACKSLASH = Collections.unmodifiableList(Arrays.asList("\\", "%5c", "%5C"));
+
+    private boolean blockSemicolon = true;
+
+    private boolean blockBackslash = true;
+
+    private boolean blockNonAscii = true;
+
+    @Override
+    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
+        String uri = WebUtils.toHttp(request).getRequestURI();
+        return !containsSemicolon(uri)
+            && !containsBackslash(uri)
+            && !containsNonAsciiCharacters(uri);
+    }
+
+    @Override
+    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
+        WebUtils.toHttp(response).sendError(400, "Invalid request");
+        return false;
+    }
+
+    private boolean containsSemicolon(String uri) {
+        if (isBlockSemicolon()) {
+            return SEMICOLON.stream().anyMatch(uri::contains);
+        }
+        return false;
+    }
+
+    private boolean containsBackslash(String uri) {
+        if (isBlockBackslash()) {
+            return BACKSLASH.stream().anyMatch(uri::contains);
+        }
+        return false;
+    }
+
+    private boolean containsNonAsciiCharacters(String uri) {
+        if (isBlockNonAscii()) {
+            return !containsOnlyPrintableAsciiCharacters(uri);
+        }
+        return false;
+    }
+
+    private static boolean containsOnlyPrintableAsciiCharacters(String uri) {
+        int length = uri.length();
+        for (int i = 0; i < length; i++) {
+            char c = uri.charAt(i);
+            if (c < '\u0020' || c > '\u007e') {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public boolean isBlockSemicolon() {
+        return blockSemicolon;
+    }
+
+    public void setBlockSemicolon(boolean blockSemicolon) {
+        this.blockSemicolon = blockSemicolon;
+    }
+
+    public boolean isBlockBackslash() {
+        return blockBackslash;
+    }
+
+    public void setBlockBackslash(boolean blockBackslash) {
+        this.blockBackslash = blockBackslash;
+    }
+
+    public boolean isBlockNonAscii() {
+        return blockNonAscii;
+    }
+
+    public void setBlockNonAscii(boolean blockNonAscii) {
+        this.blockNonAscii = blockNonAscii;
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilter.java b/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilter.java
index 036f62f..be77f06 100644
--- a/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilter.java
+++ b/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilter.java
@@ -19,6 +19,7 @@
 package org.apache.shiro.web.filter.mgt;
 
 import org.apache.shiro.util.ClassUtils;
+import org.apache.shiro.web.filter.InvalidRequestFilter;
 import org.apache.shiro.web.filter.authc.*;
 import org.apache.shiro.web.filter.authz.*;
 import org.apache.shiro.web.filter.session.NoSessionCreationFilter;
@@ -48,7 +49,8 @@
     rest(HttpMethodPermissionFilter.class),
     roles(RolesAuthorizationFilter.class),
     ssl(SslFilter.class),
-    user(UserFilter.class);
+    user(UserFilter.class),
+    invalidRequest(InvalidRequestFilter.class);
 
     private final Class<? extends Filter> filterClass;
 
diff --git a/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilterChainManager.java b/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilterChainManager.java
index a880108..f289a79 100644
--- a/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilterChainManager.java
+++ b/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilterChainManager.java
@@ -30,8 +30,10 @@
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
 import javax.servlet.ServletException;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -52,17 +54,21 @@
 
     private Map<String, Filter> filters; //pool of filters available for creating chains
 
+    private List<String> globalFilterNames; // list of filters to prepend to every chain
+
     private Map<String, NamedFilterList> filterChains; //key: chain name, value: chain
 
     public DefaultFilterChainManager() {
         this.filters = new LinkedHashMap<String, Filter>();
         this.filterChains = new LinkedHashMap<String, NamedFilterList>();
+        this.globalFilterNames = new ArrayList<>();
         addDefaultFilters(false);
     }
 
     public DefaultFilterChainManager(FilterConfig filterConfig) {
         this.filters = new LinkedHashMap<String, Filter>();
         this.filterChains = new LinkedHashMap<String, NamedFilterList>();
+        this.globalFilterNames = new ArrayList<>();
         setFilterConfig(filterConfig);
         addDefaultFilters(true);
     }
@@ -115,6 +121,15 @@
         addFilter(name, filter, init, true);
     }
 
+    public void createDefaultChain(String chainName) {
+        // only create the defaultChain if we don't have a chain with this name already
+        // (the global filters will already be in that chain)
+        if (!getChainNames().contains(chainName) && !CollectionUtils.isEmpty(globalFilterNames)) {
+            // add each of global filters
+            globalFilterNames.stream().forEach(filterName -> addToChain(chainName, filterName));
+        }
+    }
+
     public void createChain(String chainName, String chainDefinition) {
         if (!StringUtils.hasText(chainName)) {
             throw new NullPointerException("chainName cannot be null or empty.");
@@ -124,7 +139,12 @@
         }
 
         if (log.isDebugEnabled()) {
-            log.debug("Creating chain [" + chainName + "] from String definition [" + chainDefinition + "]");
+            log.debug("Creating chain [" + chainName + "] with global filters " + globalFilterNames + " and from String definition [" + chainDefinition + "]");
+        }
+
+        // first add each of global filters
+        if (!CollectionUtils.isEmpty(globalFilterNames)) {
+            globalFilterNames.stream().forEach(filterName -> addToChain(chainName, filterName));
         }
 
         //parse the value by tokenizing it to get the resulting filter-specific config entries
@@ -273,6 +293,21 @@
         chain.add(filter);
     }
 
+    public void setGlobalFilters(List<String> globalFilterNames) throws ConfigurationException {
+        // validate each filter name
+        if (!CollectionUtils.isEmpty(globalFilterNames)) {
+            for (String filterName : globalFilterNames) {
+                Filter filter = filters.get(filterName);
+                if (filter == null) {
+                    throw new ConfigurationException("There is no filter with name '" + filterName +
+                                                     "' to apply to the global filters in the pool of available Filters.  Ensure a " +
+                                                     "filter with that name/path has first been registered with the addFilter method(s).");
+                }
+                this.globalFilterNames.add(filterName);
+            }
+        }
+    }
+
     protected void applyChainConfig(String chainName, Filter filter, String chainSpecificFilterConfig) {
         if (log.isDebugEnabled()) {
             log.debug("Attempting to apply path [" + chainName + "] to filter [" + filter + "] " +
diff --git a/web/src/main/java/org/apache/shiro/web/filter/mgt/FilterChainManager.java b/web/src/main/java/org/apache/shiro/web/filter/mgt/FilterChainManager.java
index ebbc716..268c723 100644
--- a/web/src/main/java/org/apache/shiro/web/filter/mgt/FilterChainManager.java
+++ b/web/src/main/java/org/apache/shiro/web/filter/mgt/FilterChainManager.java
@@ -22,6 +22,7 @@
 
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -165,6 +166,14 @@
     void createChain(String chainName, String chainDefinition);
 
     /**
+     * Creates a chain that should match any non-matched request paths, typically {@code /**} assuming an {@link AntPathMatcher} I used.
+     * @param chainName The name of the chain to create, likely {@code /**}.
+     * @since 1.6
+     * @see org.apache.shiro.lang.util.AntPathMatcher AntPathMatcher
+     */
+    void createDefaultChain(String chainName);
+
+    /**
      * Adds (appends) a filter to the filter chain identified by the given {@code chainName}.  If there is no chain
      * with the given name, a new one is created and the filter will be the first in the chain.
      *
@@ -195,4 +204,17 @@
      *                                  interface).
      */
     void addToChain(String chainName, String filterName, String chainSpecificFilterConfig) throws ConfigurationException;
+
+    /**
+     * Configures the set of named filters that will match all paths.  These filters will match BEFORE explicitly
+     * configured filter chains i.e. by calling {@link #createChain(String, String)}, {@link #addToChain(String, String)}, etc.
+     * <br>
+     * <strong>Filters configured in this list wll apply to ALL requests.</strong>
+     *
+     * @param globalFilterNames         the list of filter names to match ALL paths.
+     * @throws ConfigurationException   if one of the filter names is invalid and cannot be loaded from the set of
+     *                                  configured filters {@link #getFilters()}}.
+     * @since 1.6
+     */
+    void setGlobalFilters(List<String> globalFilterNames) throws ConfigurationException;
 }
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 8f0d5bb..7e2ed55 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
@@ -404,6 +404,7 @@
      * @since 1.0
      */
     protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
+
         FilterChain chain = origChain;
 
         FilterChainResolver resolver = getFilterChainResolver();
diff --git a/web/src/test/groovy/org/apache/shiro/web/config/IniFilterChainResolverFactoryTest.groovy b/web/src/test/groovy/org/apache/shiro/web/config/IniFilterChainResolverFactoryTest.groovy
index 6a4e276..ba9c57f 100644
--- a/web/src/test/groovy/org/apache/shiro/web/config/IniFilterChainResolverFactoryTest.groovy
+++ b/web/src/test/groovy/org/apache/shiro/web/config/IniFilterChainResolverFactoryTest.groovy
@@ -18,6 +18,10 @@
  */
 package org.apache.shiro.web.config
 
+import org.apache.shiro.web.filter.InvalidRequestFilter
+import org.apache.shiro.web.filter.mgt.DefaultFilter
+import org.hamcrest.Matchers
+
 import javax.servlet.Filter
 import javax.servlet.FilterConfig
 import javax.servlet.ServletContext
@@ -30,6 +34,7 @@
 
 import static org.easymock.EasyMock.*
 import static org.junit.Assert.*
+import static org.hamcrest.MatcherAssert.assertThat
 
 /**
  * Unit tests for the {@link IniFilterChainResolverFactory} implementation.
@@ -57,6 +62,7 @@
         assertNull factory.filterConfig
         factory.filterConfig = null
         assertNull factory.filterConfig
+        assertThat factory.globalFilters, Matchers.contains(DefaultFilter.invalidRequest.name())
     }
 
     @Test
@@ -74,6 +80,7 @@
         factory = new IniFilterChainResolverFactory(ini)
         FilterChainResolver resolver = factory.getInstance()
         assertNotNull resolver
+        assertThat resolver.filterChainManager.globalFilterNames, Matchers.contains(DefaultFilter.invalidRequest.name())
     }
 
     @Test
@@ -173,4 +180,23 @@
         assertTrue extractedFilters['filtersSectionFilter'] instanceof SslFilter
         assertTrue extractedFilters['mainSectionFilter'] instanceof FormAuthenticationFilter
     }
+
+    @Test
+    void testConfigureInvalidRequestFilter() {
+        Ini ini = new Ini()
+        ini.load("""
+        [main]
+        invalidRequest.blockBackslash = false
+        [urls]
+        /index.html = anon
+        """)
+        factory = new IniFilterChainResolverFactory(ini)
+        FilterChainResolver resolver = factory.getInstance()
+        assertNotNull resolver
+
+        def invalidRequestFilter = resolver.filterChainManager.getChain("/index.html").get(0) // this will be the invalidRequest filter
+
+        assertThat(invalidRequestFilter, Matchers.instanceOf(InvalidRequestFilter))
+        assertThat("blockSemicolon should be faluse", invalidRequestFilter.blockBackslash)
+    }
 }
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 7dcb40b..cee3cfd 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
@@ -22,8 +22,11 @@
 import org.apache.shiro.config.Ini
 import org.apache.shiro.config.SimpleBean
 import org.apache.shiro.web.filter.mgt.DefaultFilter
+import org.apache.shiro.web.filter.mgt.FilterChainManager
+import org.hamcrest.Matchers
 import org.junit.Test
 
+import static org.hamcrest.MatcherAssert.assertThat
 import static org.junit.Assert.*
 
 /**
@@ -94,4 +97,70 @@
 
         assertSame(compositeBean.simpleBean, simpleBean)
     }
+
+    @Test
+    void testDisableGlobalFilters() {
+        Ini ini = new Ini()
+        ini.load("""
+        [main]
+        filterChainResolver.globalFilters = null
+
+        [urls]
+        /index.html = anon
+        """)
+
+        def env = new IniWebEnvironment(ini:  ini)
+        env.init()
+        assertThat env.getFilterChainResolver().filterChainManager.globalFilterNames, Matchers.empty()
+    }
+
+    @Test
+    void testDefaultGlobalFilters() {
+        Ini ini = new Ini()
+        ini.load("""
+        [main]
+
+        [urls]
+        /index.html = anon
+        """)
+
+        def env = new IniWebEnvironment(ini:  ini)
+        env.init()
+        def resolver =  env.getFilterChainResolver()
+        FilterChainManager manager = resolver.filterChainManager
+        assertThat manager.globalFilterNames, Matchers.contains(DefaultFilter.invalidRequest.name())
+
+        assertThat manager.getChain("/index.html"), Matchers.contains(
+                Matchers.instanceOf(DefaultFilter.invalidRequest.filterClass),
+                Matchers.instanceOf(DefaultFilter.anon.filterClass))
+    }
+
+    @Test
+    void testCustomGlobalFilters() {
+        Ini ini = new Ini()
+        ini.load("""
+        [main]
+        stub = org.apache.shiro.web.env.FilterStub
+        filterChainResolver.globalFilters = port,invalidRequest,stub
+
+        [urls]
+        /index.html = authc
+        """)
+
+        def env = new IniWebEnvironment(ini:  ini)
+        env.init()
+        def resolver =  env.getFilterChainResolver()
+        FilterChainManager manager = resolver.filterChainManager
+        assertThat manager.globalFilterNames, Matchers.contains(
+                DefaultFilter.port.name(),
+                DefaultFilter.invalidRequest.name(),
+                "stub"
+        )
+
+        assertThat manager.getChain("/index.html"), Matchers.contains(
+                Matchers.instanceOf(DefaultFilter.port.filterClass),
+                Matchers.instanceOf(DefaultFilter.invalidRequest.filterClass),
+                Matchers.instanceOf(FilterStub),
+                Matchers.instanceOf(DefaultFilter.authc.filterClass))
+    }
 }
diff --git a/web/src/test/groovy/org/apache/shiro/web/filter/InvalidRequestFilterTest.groovy b/web/src/test/groovy/org/apache/shiro/web/filter/InvalidRequestFilterTest.groovy
new file mode 100644
index 0000000..8d0b1c0
--- /dev/null
+++ b/web/src/test/groovy/org/apache/shiro/web/filter/InvalidRequestFilterTest.groovy
@@ -0,0 +1,106 @@
+/*
+ * 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.filter
+
+import org.junit.Test
+
+import javax.servlet.http.HttpServletRequest
+
+import static org.easymock.EasyMock.expect
+import static org.easymock.EasyMock.mock
+import static org.easymock.EasyMock.replay
+import static org.hamcrest.MatcherAssert.assertThat
+
+class InvalidRequestFilterTest {
+
+    @Test
+    void defaultConfig() {
+        InvalidRequestFilter filter = new InvalidRequestFilter()
+        assertThat "filter.blockBackslash expected to be true", filter.isBlockBackslash()
+        assertThat "filter.blockNonAscii expected to be true", filter.isBlockNonAscii()
+        assertThat "filter.blockSemicolon expected to be true", filter.isBlockSemicolon()
+    }
+
+    @Test
+    void testFilterBlocks() {
+        InvalidRequestFilter filter = new InvalidRequestFilter()
+        assertPathBlocked(filter, "/\\something")
+        assertPathBlocked(filter, "/%5csomething")
+        assertPathBlocked(filter, "/%5Csomething")
+        assertPathBlocked(filter, "/;something")
+        assertPathBlocked(filter, "/%3bsomething")
+        assertPathBlocked(filter, "/%3Bsomething")
+        assertPathBlocked(filter, "/\u0019something")
+    }
+
+    @Test
+    void testFilterAllowsBackslash() {
+        InvalidRequestFilter filter = new InvalidRequestFilter()
+        filter.setBlockBackslash(false)
+        assertPathAllowed(filter, "/\\something")
+        assertPathAllowed(filter, "/%5csomething")
+        assertPathAllowed(filter, "/%5Csomething")
+        assertPathBlocked(filter, "/;something")
+        assertPathBlocked(filter, "/%3bsomething")
+        assertPathBlocked(filter, "/%3Bsomething")
+        assertPathBlocked(filter, "/\u0019something")
+    }
+
+    @Test
+    void testFilterAllowsNonAscii() {
+        InvalidRequestFilter filter = new InvalidRequestFilter()
+        filter.setBlockNonAscii(false)
+        assertPathBlocked(filter, "/\\something")
+        assertPathBlocked(filter, "/%5csomething")
+        assertPathBlocked(filter, "/%5Csomething")
+        assertPathBlocked(filter, "/;something")
+        assertPathBlocked(filter, "/%3bsomething")
+        assertPathBlocked(filter, "/%3Bsomething")
+        assertPathAllowed(filter, "/\u0019something")
+    }
+    @Test
+    void testFilterAllowsSemicolon() {
+        InvalidRequestFilter filter = new InvalidRequestFilter()
+        filter.setBlockSemicolon(false)
+        assertPathBlocked(filter, "/\\something")
+        assertPathBlocked(filter, "/%5csomething")
+        assertPathBlocked(filter, "/%5Csomething")
+        assertPathAllowed(filter, "/;something")
+        assertPathAllowed(filter, "/%3bsomething")
+        assertPathAllowed(filter, "/%3Bsomething")
+        assertPathBlocked(filter, "/\u0019something")
+    }
+
+
+    static void assertPathBlocked(InvalidRequestFilter filter, String path) {
+        assertThat "Expected path '${path}', to be blocked", !filter.isAccessAllowed(mockRequest(path), null, null)
+    }
+
+    static void assertPathAllowed(InvalidRequestFilter filter, String path) {
+        assertThat "Expected path '${path}', to be allowed", filter.isAccessAllowed(mockRequest(path), null, null)
+    }
+
+    static HttpServletRequest mockRequest(String path) {
+        HttpServletRequest request = mock(HttpServletRequest)
+        expect(request.getRequestURI()).andReturn(path)
+        replay(request)
+        return request
+    }
+}
diff --git a/web/src/test/groovy/org/apache/shiro/web/filter/mgt/DefaultFilterChainManagerTest.groovy b/web/src/test/groovy/org/apache/shiro/web/filter/mgt/DefaultFilterChainManagerTest.groovy
index 364300b..8733588 100644
--- a/web/src/test/groovy/org/apache/shiro/web/filter/mgt/DefaultFilterChainManagerTest.groovy
+++ b/web/src/test/groovy/org/apache/shiro/web/filter/mgt/DefaultFilterChainManagerTest.groovy
@@ -21,6 +21,7 @@
 import org.apache.shiro.config.ConfigurationException
 import org.apache.shiro.web.filter.authz.SslFilter
 import org.apache.shiro.web.servlet.ShiroFilter
+import org.hamcrest.Matchers
 
 import javax.servlet.Filter
 import javax.servlet.FilterChain
@@ -31,6 +32,7 @@
 
 import static org.easymock.EasyMock.*
 import static org.junit.Assert.*
+import static org.hamcrest.MatcherAssert.assertThat
 
 /**
  * Unit tests for the {@link DefaultFilterChainManager} implementation.
@@ -210,6 +212,56 @@
         assertEquals(DefaultFilter.perms.getFilterClass(), filter.getClass());
     }
 
+    @Test
+    void testWithGlobalFilters() {
+        DefaultFilterChainManager manager = new DefaultFilterChainManager()
+        manager.setGlobalFilters(["invalidRequest", "port"])
+        assertThat(manager.filterChains, Matchers.anEmptyMap())
+
+        // add a chain
+        manager.createChain("test", "authc, roles[manager], perms[\"user:read,write:12345\"")
+
+        assertThat(manager.getChain("test"), Matchers.contains(
+                Matchers.instanceOf(DefaultFilter.invalidRequest.getFilterClass()),
+                Matchers.instanceOf(DefaultFilter.port.getFilterClass()),
+                Matchers.instanceOf(DefaultFilter.authc.getFilterClass()),
+                Matchers.instanceOf(DefaultFilter.roles.getFilterClass()),
+                Matchers.instanceOf(DefaultFilter.perms.getFilterClass())
+        ))
+
+        // the  "default" chain doesn't exist until it is created
+        assertThat(manager.getChain("/**"), Matchers.nullValue())
+        // create it
+        manager.createDefaultChain("/**")
+        // verify it
+        assertThat(manager.getChain("/**"), Matchers.contains(
+                Matchers.instanceOf(DefaultFilter.invalidRequest.getFilterClass()),
+                Matchers.instanceOf(DefaultFilter.port.getFilterClass())
+        ))
+    }
+
+    @Test
+    void addDefaultChainWithSameName() {
+
+        DefaultFilterChainManager manager = new DefaultFilterChainManager()
+        manager.setGlobalFilters(["invalidRequest", "port"])
+
+        // create a chain
+        manager.createChain("test", "authc")
+
+        // create the default chain with the same name
+        manager.createDefaultChain("test")
+
+        // since the "default" chain was created with the same name as an existing chain, we could end up adding the
+        // global filters to the chain twice, test to verify it is only once
+        assertThat(manager.getChain("test"), Matchers.contains(
+                Matchers.instanceOf(DefaultFilter.invalidRequest.getFilterClass()),
+                Matchers.instanceOf(DefaultFilter.port.getFilterClass()),
+                Matchers.instanceOf(DefaultFilter.authc.getFilterClass())
+        ))
+
+    }
+
     /**
      * Helps assert <a href="https://issues.apache.org/jira/browse/SHIRO-429">SHIRO-429</a>
      * @since 1.2.2
diff --git a/web/src/test/java/org/apache/shiro/web/env/FilterStub.java b/web/src/test/java/org/apache/shiro/web/env/FilterStub.java
new file mode 100644
index 0000000..1330974
--- /dev/null
+++ b/web/src/test/java/org/apache/shiro/web/env/FilterStub.java
@@ -0,0 +1,45 @@
+/*
+ * 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.env;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import java.io.IOException;
+
+public class FilterStub implements Filter {
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+
+    }
+
+    @Override
+    public void destroy() {
+
+    }
+}