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

lprimak pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/shiro.git

commit 42aa34467ad30f085fc8e003042db0c397fe8d57
Author: lprimak <[email protected]>
AuthorDate: Sat Jun 20 14:00:47 2026 -0500

    bugfix(guice-web): now matching paths with trailing slash stripped
---
 .../shiro/guice/web/SimpleFilterChainResolver.java | 41 +++++++++-------
 .../shiro/guice/web/PathMatcherBypassTest.java     | 55 ++++++++++++++++++++++
 .../guice/web/SimpleFilterChainResolverTest.java   |  8 ++--
 .../mgt/PathMatchingFilterChainResolver.java       | 22 +++++----
 4 files changed, 98 insertions(+), 28 deletions(-)

diff --git 
a/support/guice/src/main/java/org/apache/shiro/guice/web/SimpleFilterChainResolver.java
 
b/support/guice/src/main/java/org/apache/shiro/guice/web/SimpleFilterChainResolver.java
index bca3a4c79..3981f4db6 100644
--- 
a/support/guice/src/main/java/org/apache/shiro/guice/web/SimpleFilterChainResolver.java
+++ 
b/support/guice/src/main/java/org/apache/shiro/guice/web/SimpleFilterChainResolver.java
@@ -33,6 +33,7 @@ import com.google.inject.Key;
 import org.apache.shiro.util.PatternMatcher;
 import org.apache.shiro.web.filter.mgt.FilterChainResolver;
 import org.apache.shiro.web.util.WebUtils;
+import static 
org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver.removeTrailingSlash;
 
 class SimpleFilterChainResolver implements FilterChainResolver {
     private final Map<String, Key<? extends Filter>[]> chains;
@@ -46,26 +47,34 @@ class SimpleFilterChainResolver implements 
FilterChainResolver {
     }
 
     public FilterChain getChain(ServletRequest request, ServletResponse 
response, final FilterChain originalChain) {
-        String path = 
WebUtils.getPathWithinApplication(WebUtils.toHttp(request));
-        for (final String pathPattern : chains.keySet()) {
-            if (patternMatcher.matches(pathPattern, path)) {
-                final Iterator<Key<? extends Filter>> chain = 
Arrays.asList(chains.get(pathPattern)).iterator();
-                return new SimpleFilterChain(originalChain, new 
Iterator<Filter>() {
-                    public boolean hasNext() {
-                        return chain.hasNext();
-                    }
+        String requestURI = 
WebUtils.getPathWithinApplication(WebUtils.toHttp(request));
+        final String requestURINoTrailingSlash = 
removeTrailingSlash(requestURI);
 
-                    public Filter next() {
-                        return injector.getInstance(chain.next());
-                    }
-
-                    public void remove() {
-                        throw new UnsupportedOperationException();
-                    }
-                });
+        for (String pathPattern : chains.keySet()) {
+            if (patternMatcher.matches(pathPattern, requestURI)) {
+                return proxy(originalChain, pathPattern);
+            } else {
+                pathPattern = removeTrailingSlash(pathPattern);
+                if (patternMatcher.matches(pathPattern, 
requestURINoTrailingSlash)) {
+                    return proxy(originalChain, pathPattern);
+                }
             }
         }
         return null;
     }
 
+    private FilterChain proxy(FilterChain originalChain, String pathPattern) {
+        final Iterator<Key<? extends Filter>> chain = 
Arrays.asList(chains.get(pathPattern)).iterator();
+        return new SimpleFilterChain(originalChain, new Iterator<>() {
+            public boolean hasNext() {
+                return chain.hasNext();
+            }
+            public Filter next() {
+                return injector.getInstance(chain.next());
+            }
+            public void remove() {
+                throw new UnsupportedOperationException();
+            }
+        });
+    }
 }
diff --git 
a/support/guice/src/test/java/org/apache/shiro/guice/web/PathMatcherBypassTest.java
 
b/support/guice/src/test/java/org/apache/shiro/guice/web/PathMatcherBypassTest.java
new file mode 100644
index 000000000..8b7ff38cb
--- /dev/null
+++ 
b/support/guice/src/test/java/org/apache/shiro/guice/web/PathMatcherBypassTest.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.guice.web;
+
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.shiro.util.AntPathMatcher;
+import org.junit.jupiter.api.Test;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class PathMatcherBypassTest {
+    @Test
+    void guicePathBypass() {
+        AntPathMatcher matcher = new AntPathMatcher();
+        @SuppressWarnings("unchecked")
+        Key<? extends Filter>[] adminChain = (Key<? extends Filter>[]) new 
Key<?>[]{ Key.get(Filter.class) };
+        Map<String, Key<? extends Filter>[]> chains = new LinkedHashMap<>();
+        chains.put("/admin/foo", adminChain);
+
+        Injector injector = mock(Injector.class);
+        var resolver = new SimpleFilterChainResolver(chains, injector, 
matcher);
+        FilterChain orig = mock(FilterChain.class);
+
+        // Case 2: trailing slash bypass
+        HttpServletRequest req2 = mock(HttpServletRequest.class);
+        when(req2.getServletPath()).thenReturn("");
+        when(req2.getPathInfo()).thenReturn("/admin/foo/");
+        FilterChain r2 = resolver.getChain(req2, 
mock(HttpServletResponse.class), orig);
+        assertThat(r2).isNotNull();
+    }
+}
diff --git 
a/support/guice/src/test/java/org/apache/shiro/guice/web/SimpleFilterChainResolverTest.java
 
b/support/guice/src/test/java/org/apache/shiro/guice/web/SimpleFilterChainResolverTest.java
index e5768f793..24e6d9ee1 100644
--- 
a/support/guice/src/test/java/org/apache/shiro/guice/web/SimpleFilterChainResolverTest.java
+++ 
b/support/guice/src/test/java/org/apache/shiro/guice/web/SimpleFilterChainResolverTest.java
@@ -87,7 +87,7 @@ public class SimpleFilterChainResolverTest {
 
         expect(request.getCharacterEncoding()).andStubReturn(null);
 
-        expect(patternMatcher.matches(chainOne, "/mychain")).andReturn(false);
+        expect(patternMatcher.matches(chainOne, 
"/mychain")).andReturn(false).times(2);
         expect(patternMatcher.matches(chainTwo, "/mychain")).andReturn(true);
 
         Filter filter2a = ctrl.createMock(Filter.class);
@@ -118,9 +118,9 @@ public class SimpleFilterChainResolverTest {
 
         expect(request.getCharacterEncoding()).andStubReturn(null);
 
-        expect(patternMatcher.matches(chainOne, "/nochain")).andReturn(false);
-        expect(patternMatcher.matches(chainTwo, "/nochain")).andReturn(false);
-        expect(patternMatcher.matches(chainThree, 
"/nochain")).andReturn(false);
+        expect(patternMatcher.matches(chainOne, 
"/nochain")).andReturn(false).times(2);
+        expect(patternMatcher.matches(chainTwo, 
"/nochain")).andReturn(false).times(2);
+        expect(patternMatcher.matches(chainThree, 
"/nochain")).andReturn(false).times(2);
 
         ctrl.replay();
 
diff --git 
a/web/src/main/java/org/apache/shiro/web/filter/mgt/PathMatchingFilterChainResolver.java
 
b/web/src/main/java/org/apache/shiro/web/filter/mgt/PathMatchingFilterChainResolver.java
index 4a6fbe10a..dda7f4575 100644
--- 
a/web/src/main/java/org/apache/shiro/web/filter/mgt/PathMatchingFilterChainResolver.java
+++ 
b/web/src/main/java/org/apache/shiro/web/filter/mgt/PathMatchingFilterChainResolver.java
@@ -153,6 +153,20 @@ public class PathMatchingFilterChainResolver implements 
FilterChainResolver {
         return null;
     }
 
+    /**
+     * Removes the trailing slash from the given path if it exists and is not 
the root path ("/").
+
+     * @param path input
+     * @return stripped path
+     */
+    public static String removeTrailingSlash(String path) {
+        if (path != null && !DEFAULT_PATH_SEPARATOR.equals(path)
+                && path.endsWith(DEFAULT_PATH_SEPARATOR)) {
+            return path.substring(0, path.length() - 1);
+        }
+        return path;
+    }
+
     /**
      * Returns {@code true} if an incoming request path (the {@code path} 
argument)
      * matches a configured filter chain path (the {@code pattern} argument), 
{@code false} otherwise.
@@ -186,12 +200,4 @@ public class PathMatchingFilterChainResolver implements 
FilterChainResolver {
     protected String getPathWithinApplication(ServletRequest request) {
         return WebUtils.getPathWithinApplication(WebUtils.toHttp(request));
     }
-
-    private static String removeTrailingSlash(String path) {
-        if (path != null && !DEFAULT_PATH_SEPARATOR.equals(path)
-                && path.endsWith(DEFAULT_PATH_SEPARATOR)) {
-            return path.substring(0, path.length() - 1);
-        }
-        return path;
-    }
 }

Reply via email to