Merge pull request #262 from coheigea/jetty

Update Jetty to 9.4.33.v20201020 to fix CVE-2020-27216
diff --git a/samples/spring-mvc/src/main/java/org/apache/shiro/samples/spring/config/ApplicationConfig.java b/samples/spring-mvc/src/main/java/org/apache/shiro/samples/spring/config/ApplicationConfig.java
index 67a5cca..9c06eae 100644
--- a/samples/spring-mvc/src/main/java/org/apache/shiro/samples/spring/config/ApplicationConfig.java
+++ b/samples/spring-mvc/src/main/java/org/apache/shiro/samples/spring/config/ApplicationConfig.java
@@ -30,6 +30,7 @@
 import org.apache.shiro.spring.remoting.SecureRemoteInvocationExecutor;
 import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
 import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
+import org.apache.shiro.spring.web.config.ShiroRequestMappingConfig;
 import org.apache.shiro.spring.web.config.ShiroWebConfiguration;
 import org.apache.shiro.spring.web.config.ShiroWebFilterConfiguration;
 import org.springframework.context.annotation.Bean;
@@ -53,7 +54,8 @@
         ShiroWebConfiguration.class,
         ShiroWebFilterConfiguration.class,
         JspViewsConfig.class,
-        RemotingServletConfig.class})
+        RemotingServletConfig.class,
+        ShiroRequestMappingConfig.class})
 @ComponentScan("org.apache.shiro.samples.spring")
 public class ApplicationConfig {
 
diff --git a/samples/spring-mvc/src/main/java/org/apache/shiro/samples/spring/config/JspViewsConfig.java b/samples/spring-mvc/src/main/java/org/apache/shiro/samples/spring/config/JspViewsConfig.java
index 551089c..34f9912 100644
--- a/samples/spring-mvc/src/main/java/org/apache/shiro/samples/spring/config/JspViewsConfig.java
+++ b/samples/spring-mvc/src/main/java/org/apache/shiro/samples/spring/config/JspViewsConfig.java
@@ -25,7 +25,7 @@
 import org.springframework.web.servlet.ViewResolver;
 import org.springframework.web.servlet.config.annotation.EnableWebMvc;
 import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
-import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 import org.springframework.web.servlet.view.InternalResourceViewResolver;
 import org.springframework.web.servlet.view.JstlView;
 
@@ -35,7 +35,7 @@
 @Configuration
 @ComponentScan("org.apache.shiro.samples.spring")
 @EnableWebMvc
-public class JspViewsConfig extends WebMvcConfigurerAdapter {
+public class JspViewsConfig implements WebMvcConfigurer {
 
     @Bean
     @Order(1)
diff --git a/support/spring-boot/spring-boot-web-starter/src/main/java/org/apache/shiro/spring/config/web/autoconfigure/ShiroWebAutoConfiguration.java b/support/spring-boot/spring-boot-web-starter/src/main/java/org/apache/shiro/spring/config/web/autoconfigure/ShiroWebAutoConfiguration.java
index 4ab440c..3b89b63 100644
--- a/support/spring-boot/spring-boot-web-starter/src/main/java/org/apache/shiro/spring/config/web/autoconfigure/ShiroWebAutoConfiguration.java
+++ b/support/spring-boot/spring-boot-web-starter/src/main/java/org/apache/shiro/spring/config/web/autoconfigure/ShiroWebAutoConfiguration.java
@@ -33,9 +33,11 @@
 import org.apache.shiro.session.mgt.SessionManager;
 import org.apache.shiro.session.mgt.eis.SessionDAO;
 import org.apache.shiro.spring.boot.autoconfigure.ShiroAutoConfiguration;
+import org.apache.shiro.spring.web.ShiroUrlPathHelper;
 import org.apache.shiro.spring.web.config.AbstractShiroWebConfiguration;
 import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
 import org.apache.shiro.web.servlet.Cookie;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
 import org.springframework.boot.autoconfigure.AutoConfigureBefore;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -47,6 +49,7 @@
  */
 @Configuration
 @AutoConfigureBefore(ShiroAutoConfiguration.class)
+@AutoConfigureAfter(ShiroWebMvcAutoConfiguration.class)
 @ConditionalOnProperty(name = "shiro.web.enabled", matchIfMissing = true)
 public class ShiroWebAutoConfiguration extends AbstractShiroWebConfiguration {
 
@@ -147,4 +150,11 @@
     protected ShiroFilterChainDefinition shiroFilterChainDefinition() {
         return super.shiroFilterChainDefinition();
     }
+
+    @Bean
+    @ConditionalOnMissingBean
+    @Override
+    protected ShiroUrlPathHelper shiroUrlPathHelper() {
+        return super.shiroUrlPathHelper();
+    }
 }
diff --git a/support/spring-boot/spring-boot-web-starter/src/main/java/org/apache/shiro/spring/config/web/autoconfigure/ShiroWebMvcAutoConfiguration.java b/support/spring-boot/spring-boot-web-starter/src/main/java/org/apache/shiro/spring/config/web/autoconfigure/ShiroWebMvcAutoConfiguration.java
new file mode 100644
index 0000000..26fdeb7
--- /dev/null
+++ b/support/spring-boot/spring-boot-web-starter/src/main/java/org/apache/shiro/spring/config/web/autoconfigure/ShiroWebMvcAutoConfiguration.java
@@ -0,0 +1,32 @@
+/*
+ * 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.config.web.autoconfigure;
+
+import org.apache.shiro.spring.web.config.ShiroRequestMappingConfig;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+@Configuration
+@ConditionalOnClass(RequestMappingHandlerMapping.class)
+@Import(ShiroRequestMappingConfig.class)
+@ConditionalOnProperty(name = "shiro.web.enabled", matchIfMissing = true)
+public class ShiroWebMvcAutoConfiguration { }
diff --git a/support/spring-boot/spring-boot-web-starter/src/main/resources/META-INF/spring.factories b/support/spring-boot/spring-boot-web-starter/src/main/resources/META-INF/spring.factories
index b69324a..328062d 100644
--- a/support/spring-boot/spring-boot-web-starter/src/main/resources/META-INF/spring.factories
+++ b/support/spring-boot/spring-boot-web-starter/src/main/resources/META-INF/spring.factories
@@ -1,3 +1,4 @@
 org.springframework.boot.autoconfigure.EnableAutoConfiguration = \
   org.apache.shiro.spring.config.web.autoconfigure.ShiroWebAutoConfiguration,\
-  org.apache.shiro.spring.config.web.autoconfigure.ShiroWebFilterConfiguration
+  org.apache.shiro.spring.config.web.autoconfigure.ShiroWebFilterConfiguration, \
+  org.apache.shiro.spring.config.web.autoconfigure.ShiroWebMvcAutoConfiguration
diff --git a/support/spring/pom.xml b/support/spring/pom.xml
index 254373f..78d8b65 100644
--- a/support/spring/pom.xml
+++ b/support/spring/pom.xml
@@ -50,6 +50,16 @@
             <artifactId>spring-context</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-webmvc</artifactId>
+            <optional>true</optional>
+        </dependency>
         <!-- Test dependencies -->
         <dependency>
             <groupId>org.slf4j</groupId>
@@ -72,11 +82,6 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>org.springframework</groupId>
-            <artifactId>spring-web</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
             <groupId>org.apache.shiro</groupId>
             <artifactId>shiro-aspectj</artifactId>
             <scope>test</scope>
diff --git a/support/spring/src/main/java/org/apache/shiro/spring/web/ShiroUrlPathHelper.java b/support/spring/src/main/java/org/apache/shiro/spring/web/ShiroUrlPathHelper.java
new file mode 100644
index 0000000..4b5ddbd
--- /dev/null
+++ b/support/spring/src/main/java/org/apache/shiro/spring/web/ShiroUrlPathHelper.java
@@ -0,0 +1,41 @@
+/*
+ * 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.web;
+
+import org.apache.shiro.web.util.WebUtils;
+import org.springframework.web.util.UrlPathHelper;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * A Spring UrlPathHelper that uses Shiro's path resolution logic.
+ * @since 1.7.0
+ */
+public class ShiroUrlPathHelper extends UrlPathHelper {
+
+    @Override
+    public String getPathWithinApplication(HttpServletRequest request) {
+        return WebUtils.getPathWithinApplication(request);
+    }
+
+    @Override
+    public String getPathWithinServletMapping(HttpServletRequest request) {
+        return WebUtils.getPathWithinApplication(request);
+    }
+}
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 ae2afb5..fab145f 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
@@ -24,6 +24,7 @@
 import org.apache.shiro.mgt.SubjectFactory;
 import org.apache.shiro.session.mgt.SessionManager;
 import org.apache.shiro.spring.config.AbstractShiroConfiguration;
+import org.apache.shiro.spring.web.ShiroUrlPathHelper;
 import org.apache.shiro.web.mgt.CookieRememberMeManager;
 import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
 import org.apache.shiro.web.mgt.DefaultWebSessionStorageEvaluator;
@@ -42,7 +43,7 @@
     @Value("#{ @environment['shiro.sessionManager.sessionIdCookieEnabled'] ?: true }")
     protected boolean sessionIdCookieEnabled;
 
-    @Value("#{ @environment['shiro.sessionManager.sessionIdUrlRewritingEnabled'] ?: true }")
+    @Value("#{ @environment['shiro.sessionManager.sessionIdUrlRewritingEnabled'] ?: false }")
     protected boolean sessionIdUrlRewritingEnabled;
 
     @Value("#{ @environment['shiro.userNativeSessionManager'] ?: false }")
@@ -181,4 +182,8 @@
         chainDefinition.addPathDefinition("/**", "authc");
         return chainDefinition;
     }
+
+    protected ShiroUrlPathHelper shiroUrlPathHelper() {
+        return new ShiroUrlPathHelper();
+    }
 }
diff --git a/support/spring/src/main/java/org/apache/shiro/spring/web/config/ShiroRequestMappingConfig.java b/support/spring/src/main/java/org/apache/shiro/spring/web/config/ShiroRequestMappingConfig.java
new file mode 100644
index 0000000..317cb4d
--- /dev/null
+++ b/support/spring/src/main/java/org/apache/shiro/spring/web/config/ShiroRequestMappingConfig.java
@@ -0,0 +1,31 @@
+/*
+ * 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.web.config;
+
+import org.apache.shiro.spring.web.ShiroUrlPathHelper;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+@Configuration
+public class ShiroRequestMappingConfig {
+
+    public ShiroRequestMappingConfig(RequestMappingHandlerMapping requestMappingHandlerMapping) {
+        requestMappingHandlerMapping.setUrlPathHelper(new ShiroUrlPathHelper());
+    }
+}
diff --git a/support/spring/src/main/java/org/apache/shiro/spring/web/config/ShiroWebConfiguration.java b/support/spring/src/main/java/org/apache/shiro/spring/web/config/ShiroWebConfiguration.java
index dc57b22..952a26a 100644
--- a/support/spring/src/main/java/org/apache/shiro/spring/web/config/ShiroWebConfiguration.java
+++ b/support/spring/src/main/java/org/apache/shiro/spring/web/config/ShiroWebConfiguration.java
@@ -26,6 +26,7 @@
 import org.apache.shiro.session.mgt.SessionFactory;
 import org.apache.shiro.session.mgt.SessionManager;
 import org.apache.shiro.session.mgt.eis.SessionDAO;
+import org.apache.shiro.spring.web.ShiroUrlPathHelper;
 import org.apache.shiro.web.servlet.Cookie;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -122,4 +123,10 @@
     protected ShiroFilterChainDefinition shiroFilterChainDefinition() {
         return super.shiroFilterChainDefinition();
     }
+
+    @Bean
+    @Override
+    protected ShiroUrlPathHelper shiroUrlPathHelper() {
+        return super.shiroUrlPathHelper();
+    }
 }
diff --git a/support/spring/src/test/groovy/org/apache/shiro/spring/web/ShiroUrlPathHelperTest.groovy b/support/spring/src/test/groovy/org/apache/shiro/spring/web/ShiroUrlPathHelperTest.groovy
new file mode 100644
index 0000000..08eeb79
--- /dev/null
+++ b/support/spring/src/test/groovy/org/apache/shiro/spring/web/ShiroUrlPathHelperTest.groovy
@@ -0,0 +1,46 @@
+/*
+ * 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.web
+
+import org.junit.Test
+import org.springframework.mock.web.MockHttpServletRequest
+import org.springframework.web.util.UrlPathHelper
+
+import static org.hamcrest.MatcherAssert.assertThat
+import static org.hamcrest.Matchers.equalTo
+
+/**
+ * Tests a couple known differences between the stock and the ShiroUrlPathHelper
+ */
+class ShiroUrlPathHelperTest {
+
+    @Test
+    void testGetPathWithinApplication() {
+        MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo/%2e%2e")
+        assertThat new UrlPathHelper().getPathWithinApplication(request), equalTo("/foo/..")
+        assertThat new ShiroUrlPathHelper().getPathWithinApplication(request), equalTo("/")
+    }
+
+    @Test
+    void testGetPathWithinServletMapping() {
+        MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo/%2e%2e")
+        assertThat new UrlPathHelper().getPathWithinServletMapping(request), equalTo("/foo/..")
+        assertThat new ShiroUrlPathHelper().getPathWithinServletMapping(request), equalTo("/")
+    }
+}
\ No newline at end of file
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
index 3d229e9..63da6d4 100644
--- a/web/src/main/java/org/apache/shiro/web/filter/InvalidRequestFilter.java
+++ b/web/src/main/java/org/apache/shiro/web/filter/InvalidRequestFilter.java
@@ -19,10 +19,12 @@
 
 package org.apache.shiro.web.filter;
 
+import org.apache.shiro.lang.util.StringUtils;
 import org.apache.shiro.web.util.WebUtils;
 
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -48,16 +50,24 @@
 
     private boolean blockSemicolon = true;
 
-    private boolean blockBackslash = true;
+    private boolean blockBackslash = !Boolean.getBoolean(WebUtils.ALLOW_BACKSLASH);
 
     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);
+    protected boolean isAccessAllowed(ServletRequest req, ServletResponse response, Object mappedValue) throws Exception {
+        HttpServletRequest request = WebUtils.toHttp(req);
+        // check the original and decoded values
+        return isValid(request.getRequestURI())      // user request string (not decoded)
+                && isValid(request.getServletPath()) // decoded servlet part
+                && isValid(request.getPathInfo());   // decoded path info (may be null)
+    }
+
+    private boolean isValid(String uri) {
+        return !StringUtils.hasText(uri)
+               || ( !containsSemicolon(uri)
+                 && !containsBackslash(uri)
+                 && !containsNonAsciiCharacters(uri));
     }
 
     @Override
diff --git a/web/src/main/java/org/apache/shiro/web/session/mgt/DefaultWebSessionManager.java b/web/src/main/java/org/apache/shiro/web/session/mgt/DefaultWebSessionManager.java
index eb7eda1..9aa275a 100644
--- a/web/src/main/java/org/apache/shiro/web/session/mgt/DefaultWebSessionManager.java
+++ b/web/src/main/java/org/apache/shiro/web/session/mgt/DefaultWebSessionManager.java
@@ -58,7 +58,7 @@
         cookie.setHttpOnly(true); //more secure, protects against XSS attacks
         this.sessionIdCookie = cookie;
         this.sessionIdCookieEnabled = true;
-        this.sessionIdUrlRewritingEnabled = true;
+        this.sessionIdUrlRewritingEnabled = false;
     }
 
     public Cookie getSessionIdCookie() {
diff --git a/web/src/main/java/org/apache/shiro/web/util/WebUtils.java b/web/src/main/java/org/apache/shiro/web/util/WebUtils.java
index abc27c4..992f7ad 100644
--- a/web/src/main/java/org/apache/shiro/web/util/WebUtils.java
+++ b/web/src/main/java/org/apache/shiro/web/util/WebUtils.java
@@ -57,6 +57,8 @@
     public static final String SERVLET_REQUEST_KEY = ServletRequest.class.getName() + "_SHIRO_THREAD_CONTEXT_KEY";
     public static final String SERVLET_RESPONSE_KEY = ServletResponse.class.getName() + "_SHIRO_THREAD_CONTEXT_KEY";
 
+    public static final String ALLOW_BACKSLASH = "org.apache.shiro.web.ALLOW_BACKSLASH";
+
     /**
      * {@link org.apache.shiro.session.Session Session} key used to save a request and later restore it, for example when redirecting to a
      * requested page after login, equal to {@code shiroSavedRequest}.
@@ -163,7 +165,7 @@
      * @return normalized path
      */
     public static String normalize(String path) {
-        return normalize(path, true);
+        return normalize(path, Boolean.getBoolean(ALLOW_BACKSLASH));
     }
 
     /**
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
index 8d0b1c0..c7a0525 100644
--- a/web/src/test/groovy/org/apache/shiro/web/filter/InvalidRequestFilterTest.groovy
+++ b/web/src/test/groovy/org/apache/shiro/web/filter/InvalidRequestFilterTest.groovy
@@ -19,6 +19,7 @@
 
 package org.apache.shiro.web.filter
 
+import org.apache.shiro.web.RestoreSystemProperties
 import org.junit.Test
 
 import javax.servlet.http.HttpServletRequest
@@ -39,6 +40,25 @@
     }
 
     @Test
+    void systemPropertyAllowBackslash() {
+        RestoreSystemProperties.withProperties(["org.apache.shiro.web.ALLOW_BACKSLASH": "true"]) {
+            InvalidRequestFilter filter = new InvalidRequestFilter()
+            assertThat "filter.blockBackslash expected to be false", !filter.isBlockBackslash()
+        }
+
+        RestoreSystemProperties.withProperties(["org.apache.shiro.web.ALLOW_BACKSLASH": ""]) {
+            InvalidRequestFilter filter = new InvalidRequestFilter()
+            assertThat "filter.blockBackslash expected to be false", filter.isBlockBackslash()
+        }
+
+        RestoreSystemProperties.withProperties(["org.apache.shiro.web.ALLOW_BACKSLASH": "false"]) {
+            InvalidRequestFilter filter = new InvalidRequestFilter()
+            assertThat "filter.blockBackslash expected to be false", filter.isBlockBackslash()
+        }
+    }
+
+
+    @Test
     void testFilterBlocks() {
         InvalidRequestFilter filter = new InvalidRequestFilter()
         assertPathBlocked(filter, "/\\something")
@@ -48,6 +68,9 @@
         assertPathBlocked(filter, "/%3bsomething")
         assertPathBlocked(filter, "/%3Bsomething")
         assertPathBlocked(filter, "/\u0019something")
+
+        assertPathBlocked(filter, "/something", "/;something")
+        assertPathBlocked(filter, "/something", "/something", "/;")
     }
 
     @Test
@@ -61,6 +84,9 @@
         assertPathBlocked(filter, "/%3bsomething")
         assertPathBlocked(filter, "/%3Bsomething")
         assertPathBlocked(filter, "/\u0019something")
+
+        assertPathAllowed(filter, "/something", "/\\something")
+        assertPathAllowed(filter, "/something", "/something", "/\\")
     }
 
     @Test
@@ -74,6 +100,9 @@
         assertPathBlocked(filter, "/%3bsomething")
         assertPathBlocked(filter, "/%3Bsomething")
         assertPathAllowed(filter, "/\u0019something")
+
+        assertPathAllowed(filter, "/something", "/\u0019something")
+        assertPathAllowed(filter, "/something", "/something", "/\u0019")
     }
     @Test
     void testFilterAllowsSemicolon() {
@@ -86,20 +115,27 @@
         assertPathAllowed(filter, "/%3bsomething")
         assertPathAllowed(filter, "/%3Bsomething")
         assertPathBlocked(filter, "/\u0019something")
+
+        assertPathAllowed(filter, "/something", "/;something")
+        assertPathAllowed(filter, "/something", "/something", "/;")
     }
 
 
-    static void assertPathBlocked(InvalidRequestFilter filter, String path) {
-        assertThat "Expected path '${path}', to be blocked", !filter.isAccessAllowed(mockRequest(path), null, null)
+    static void assertPathBlocked(InvalidRequestFilter filter, String requestUri, String servletPath = requestUri, String pathInfo = null) {
+        assertThat "Expected path '${requestUri}', to be blocked", !filter.isAccessAllowed(mockRequest(requestUri, servletPath, pathInfo), null, null)
     }
 
-    static void assertPathAllowed(InvalidRequestFilter filter, String path) {
-        assertThat "Expected path '${path}', to be allowed", filter.isAccessAllowed(mockRequest(path), null, null)
+    static void assertPathAllowed(InvalidRequestFilter filter, String requestUri, String servletPath = requestUri, String pathInfo = null) {
+        assertThat "Expected requestUri '${requestUri}', to be allowed", filter.isAccessAllowed(mockRequest(requestUri, servletPath, pathInfo), null, null)
     }
 
-    static HttpServletRequest mockRequest(String path) {
+    static HttpServletRequest mockRequest(String requestUri, String servletPath, String pathInfo) {
         HttpServletRequest request = mock(HttpServletRequest)
-        expect(request.getRequestURI()).andReturn(path)
+        expect(request.getRequestURI()).andReturn(requestUri)
+        expect(request.getServletPath()).andReturn(servletPath).anyTimes()
+        expect(request.getPathInfo()).andReturn(pathInfo).anyTimes()
+        expect(request.getAttribute("javax.servlet.include.servlet_path")).andReturn(servletPath)
+        expect(request.getAttribute("javax.servlet.include.path_info")).andReturn(pathInfo)
         replay(request)
         return request
     }
diff --git a/web/src/test/groovy/org/apache/shiro/web/session/mgt/DefaultWebSessionManagerTest.groovy b/web/src/test/groovy/org/apache/shiro/web/session/mgt/DefaultWebSessionManagerTest.groovy
index 841569f..35b3120 100644
--- a/web/src/test/groovy/org/apache/shiro/web/session/mgt/DefaultWebSessionManagerTest.groovy
+++ b/web/src/test/groovy/org/apache/shiro/web/session/mgt/DefaultWebSessionManagerTest.groovy
@@ -127,7 +127,7 @@
                 ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
         request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
         request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
-        request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, Boolean.TRUE);
+        request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, Boolean.FALSE);
 
         replay(cookie);
         replay(request);
@@ -147,6 +147,7 @@
         Cookie cookie = createMock(Cookie.class);
         mgr.setSessionIdCookie(cookie);
         mgr.setSessionIdCookieEnabled(false);
+        mgr.setSessionIdUrlRewritingEnabled(true)
 
         //we should not have any reads from the cookie fields - if we do, this test case will fail.
 
@@ -182,6 +183,7 @@
         Cookie cookie = createMock(Cookie.class);
         mgr.setSessionIdCookie(cookie);
         mgr.setSessionIdCookieEnabled(false);
+        mgr.setSessionIdUrlRewritingEnabled(true)
 
         //we should not have any reads from the cookie fields - if we do, this test case will fail.
 
@@ -218,6 +220,7 @@
     public void testGetSessionIdFromRequestUriPathSegmentParam() {
 
         mgr.setSessionIdCookieEnabled(false);
+        mgr.setSessionIdUrlRewritingEnabled(true)
 
         HttpServletRequest request = createMock(HttpServletRequest.class);
         HttpServletResponse response = createMock(HttpServletResponse.class);
diff --git a/web/src/test/groovy/org/apache/shiro/web/util/WebUtilsTest.groovy b/web/src/test/groovy/org/apache/shiro/web/util/WebUtilsTest.groovy
index 7956a10..b501605 100644
--- a/web/src/test/groovy/org/apache/shiro/web/util/WebUtilsTest.groovy
+++ b/web/src/test/groovy/org/apache/shiro/web/util/WebUtilsTest.groovy
@@ -18,12 +18,15 @@
  */
 package org.apache.shiro.web.util
 
+import org.apache.shiro.web.RestoreSystemProperties
+import org.hamcrest.CoreMatchers
 import org.junit.Test
 
 import javax.servlet.http.HttpServletRequest
 
 import static org.easymock.EasyMock.*
 import static org.junit.Assert.*
+import static org.hamcrest.CoreMatchers.*
 
 /**
  * Tests for {@link WebUtils}.
@@ -193,6 +196,55 @@
         doTestGetRequestURI("/context path/foobar", "/context path/foobar");
     }
 
+    @Test
+    void testNormalize() {
+        doNormalizeTest"/foobar", "/foobar"
+        doNormalizeTest "/foobar/", "/foobar/"
+        doNormalizeTest"", "/"
+        doNormalizeTest"foobar", "/foobar"
+        doNormalizeTest"//foobar", "/foobar"
+        doNormalizeTest"//foobar///", "/foobar/"
+        doNormalizeTest"/context-path/foobar", "/context-path/foobar"
+        doNormalizeTest"/context-path/foobar/", "/context-path/foobar/"
+        doNormalizeTest"//context-path/foobar", "/context-path/foobar"
+        doNormalizeTest"//context-path//foobar" ,"/context-path/foobar"
+        doNormalizeTest"//context-path/remove-one/remove-two/../../././/foobar", "/context-path/foobar"
+        doNormalizeTest"//context-path//../../././/foobar", null
+        doNormalizeTest"/context path/foobar", "/context path/foobar"
+
+        doNormalizeTest"/context path/\\foobar", "/context path/\\foobar"
+        doNormalizeTest"//context-path\\..\\../.\\.\\foobar", "/context-path\\..\\../.\\.\\foobar"
+        doNormalizeTest"//context-path\\..\\..\\.\\.\\foobar", "/context-path\\..\\..\\.\\.\\foobar"
+        doNormalizeTest"\\context-path\\..\\foobar", "/\\context-path\\..\\foobar"
+    }
+
+    @Test
+    void testNormalize_allowBackslashes() {
+        RestoreSystemProperties.withProperties(["org.apache.shiro.web.ALLOW_BACKSLASH": "true"]) {
+            doNormalizeTest"/foobar", "/foobar"
+            doNormalizeTest "/foobar/", "/foobar/"
+            doNormalizeTest"", "/"
+            doNormalizeTest"foobar", "/foobar"
+            doNormalizeTest"//foobar", "/foobar"
+            doNormalizeTest"//foobar///", "/foobar/"
+            doNormalizeTest"/context-path/foobar", "/context-path/foobar"
+            doNormalizeTest"/context-path/foobar/", "/context-path/foobar/"
+            doNormalizeTest"//context-path/foobar", "/context-path/foobar"
+            doNormalizeTest"//context-path//foobar" ,"/context-path/foobar"
+            doNormalizeTest"//context-path/remove-one/remove-two/../../././/foobar", "/context-path/foobar"
+            doNormalizeTest"//context-path//../../././/foobar", null
+            doNormalizeTest"/context path/foobar", "/context path/foobar"
+            doNormalizeTest"/context path/\\foobar", "/context path/foobar"
+            doNormalizeTest"//context-path\\..\\..\\.\\.\\foobar", null
+            doNormalizeTest"\\context-path\\..\\foobar", "/foobar"
+
+        }
+    }
+
+    void doNormalizeTest(String path, String expected) {
+        assertThat WebUtils.normalize(path), equalTo(expected)
+    }
+
     void doTestGetPathWithinApplication(String servletPath, String pathInfo, String expectedValue) {
 
         def request = createMock(HttpServletRequest)
diff --git a/web/src/test/java/org/apache/shiro/web/RestoreSystemProperties.java b/web/src/test/java/org/apache/shiro/web/RestoreSystemProperties.java
new file mode 100644
index 0000000..882e2e9
--- /dev/null
+++ b/web/src/test/java/org/apache/shiro/web/RestoreSystemProperties.java
@@ -0,0 +1,69 @@
+/*
+ * 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;
+
+import groovy.lang.Closure;
+
+import java.io.Closeable;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Wrapper that will restore System properties after test methods.
+ *
+ * Based on: https://github.com/stefanbirkner/system-rules/blob/master/src/main/java/org/junit/contrib/java/lang/system/RestoreSystemProperties.java
+ */
+public class RestoreSystemProperties implements Closeable {
+
+    private final Properties originalProperties;
+
+    public RestoreSystemProperties() {
+        originalProperties = System.getProperties();
+        System.setProperties(copyOf(originalProperties));
+    }
+
+    public void restore() {
+        System.setProperties(originalProperties);
+    }
+
+    private Properties copyOf(Properties source) {
+        Properties copy = new Properties();
+        copy.putAll(source);
+        return copy;
+    }
+
+    public static <T> T withProperties(Closure<T> closure) {
+        return withProperties(Collections.emptyMap(), closure);
+    }
+
+    public static <T> T withProperties(Map<String, String> properties, Closure<T> closure) {
+
+        try (RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties()) {
+            properties.forEach(System::setProperty);
+
+            return closure.call();
+        }
+    }
+
+    @Override
+    public void close() {
+        restore();
+    }
+}