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


The following commit(s) were added to refs/heads/main by this push:
     new 8e0e4ac7a bugfix(jakarta-ee): form resubmit logic uses more data from 
the subse… (#2885)
8e0e4ac7a is described below

commit 8e0e4ac7a7af35c8c8ec552b51cf3bd809196622
Author: Lenny Primak <[email protected]>
AuthorDate: Thu Sep 10 09:54:50 2026 -0500

    bugfix(jakarta-ee): form resubmit logic uses more data from the subse… 
(#2885)
    
    * bugfix(jakarta-ee): form resubmit logic uses more data from the 
subsequent request, rather than previous request
    
    * refactor: deleted now-obsolete whitelist check
    
    * fixed errors in AuthenticationFilterDelegate.java
---
 .../jakarta-ee/src/main/webapp/WEB-INF/web.xml     |   4 -
 .../ee/filters/AuthenticationFilterDelegate.java   |   8 +-
 .../shiro/ee/filters/FormResubmitSupport.java      | 205 +++++++--------------
 .../shiro/ee/filters/FormResubmitValidator.java    |  65 -------
 .../org/apache/shiro/ee/filters/ShiroFilter.java   |   4 +-
 .../ee/listeners/EnvironmentLoaderListener.java    |   8 -
 .../apache/shiro/ee/filters/FormSupportTest.java   |  65 ++++---
 .../shiro/web/filter/authc/NoAccessFilter.java     |  13 --
 8 files changed, 110 insertions(+), 262 deletions(-)

diff --git a/integration-tests/jakarta-ee/src/main/webapp/WEB-INF/web.xml 
b/integration-tests/jakarta-ee/src/main/webapp/WEB-INF/web.xml
index 3008d063e..cead2eaec 100644
--- a/integration-tests/jakarta-ee/src/main/webapp/WEB-INF/web.xml
+++ b/integration-tests/jakarta-ee/src/main/webapp/WEB-INF/web.xml
@@ -70,10 +70,6 @@
         <param-name>org.apache.shiro.form-resubmit.secure-cookies</param-name>
         <param-value>false</param-value>
     </context-param>
-    <context-param>
-        
<param-name>org.apache.shiro.form-resubmit.whitelist.disabled</param-name>
-        <param-value>true</param-value>
-    </context-param>
 
     <!-- Apache Shiro Security -->
     <context-param>
diff --git 
a/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/AuthenticationFilterDelegate.java
 
b/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/AuthenticationFilterDelegate.java
index fcc63912a..7cb31df49 100644
--- 
a/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/AuthenticationFilterDelegate.java
+++ 
b/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/AuthenticationFilterDelegate.java
@@ -33,15 +33,12 @@ import static 
org.apache.shiro.ee.filters.FormAuthenticationFilter.LOGIN_PREDICA
 import static 
org.apache.shiro.ee.filters.FormAuthenticationFilter.LOGIN_URL_ATTR_NAME;
 import static 
org.apache.shiro.ee.filters.FormAuthenticationFilter.LOGIN_WAITTIME_ATTR_NAME;
 import static 
org.apache.shiro.ee.filters.FormAuthenticationFilter.NO_PREDICATE;
-import static 
org.apache.shiro.ee.filters.FormAuthenticationFilter.getPathWithinApplication;
-import static org.apache.shiro.ee.filters.FormResubmitSupport.isPostRequest;
 import static 
org.apache.shiro.ee.filters.FormResubmitSupport.savePostDataForResubmit;
 import static 
org.apache.shiro.ee.filters.FormResubmitSupport.saveRequestReferer;
 import static 
org.apache.shiro.ee.filters.LogoutFilter.LOGOUT_PREDICATE_ATTR_NAME;
 import static org.apache.shiro.ee.filters.LogoutFilter.YES_PREDICATE;
 import static 
org.apache.shiro.ee.listeners.EnvironmentLoaderListener.isFormResubmitDisabled;
 import static 
org.apache.shiro.ee.listeners.EnvironmentLoaderListener.isServletNoPrincipal;
-import static 
org.apache.shiro.web.filter.authc.NoAccessFilter.FORM_RESUBMIT_CHECK_SERVLET_PATH;
 import static 
org.apache.shiro.web.jaxrs.SubjectPrincipalRequestFilter.SHIRO_WEB_JAXRS_DISABLE_PRINCIPAL_PARAM;
 
 /**
@@ -104,10 +101,7 @@ class AuthenticationFilterDelegate {
     public boolean isAccessAllowed(ServletRequest request, ServletResponse 
response, Object mappedValue) {
         Subject subject = methods.getSubject(request, response);
         boolean isAuthenticated = subject.isAuthenticated() && 
subject.getPrincipal() != null;
-        return isAuthenticated || (useRemembered && subject.isRemembered())
-                || (isPostRequest(request)
-                && 
FORM_RESUBMIT_CHECK_SERVLET_PATH.equals(getPathWithinApplication(request,
-                () -> 
WebUtils.getPathWithinApplication(WebUtils.toHttp(request)))));
+        return isAuthenticated || (useRemembered && subject.isRemembered());
     }
 
     /**
diff --git 
a/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitSupport.java
 
b/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitSupport.java
index c3e78686b..775e8f510 100644
--- 
a/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitSupport.java
+++ 
b/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitSupport.java
@@ -37,7 +37,6 @@ import static 
org.apache.shiro.ee.filters.FormResubmitSupportCookies.getSessionC
 import java.net.URISyntaxException;
 import java.time.Duration;
 import java.util.Collections;
-import org.apache.shiro.cache.CacheManager;
 import org.apache.shiro.crypto.CryptoException;
 import org.apache.shiro.ee.filters.Forms.FallbackPredicate;
 import static 
org.apache.shiro.ee.filters.FormResubmitSupportCookies.initializeCookies;
@@ -59,9 +58,7 @@ import java.util.Optional;
 import java.util.Set;
 import java.util.UUID;
 import static java.util.function.Predicate.not;
-import static 
org.apache.shiro.ee.listeners.EnvironmentLoaderListener.isFormResubmitWhitelistEnabled;
 import static org.apache.shiro.ee.listeners.IniEnvironment.hasFacesContext;
-import static 
org.apache.shiro.web.filter.authc.NoAccessFilter.FORM_RESUBMIT_CHECK_SERVLET_PATH;
 import static org.apache.shiro.web.filter.authz.PortFilter.DEFAULT_HTTP_PORT;
 import static org.apache.shiro.web.filter.authz.PortFilter.HTTP_SCHEME;
 import static org.apache.shiro.web.filter.authz.SslFilter.DEFAULT_HTTPS_PORT;
@@ -106,10 +103,8 @@ public class FormResubmitSupport {
     static final String SHIRO_FORM_DATA_KEY = "org.apache.shiro.form-data-key";
     static final String SESSION_EXPIRED_PARAMETER = 
"org.apache.shiro.sessionExpired";
     static final String FORM_IS_RESUBMITTED = 
"org.apache.shiro.form-is-resubmitted";
-    static final String FORM_RESUBMIT_WHITELIST = 
"org.apache.shiro.form-resubmit-whitelist";
     static final String FORM_RESUBMIT_BLACKLIST = 
"org.apache.shiro.form-resubmit-blacklist";
     static final String FORM_DATA_CACHE = "org.apache.shiro.form-data-cache";
-    static final String FORM_DATA_KEY_PREFIX = "formDataKey=";
     // encoded view state
     private static final String FACES_VIEW_STATE = "jakarta.faces.ViewState";
     private static final String FACES_VIEW_STATE_EQUALS = FACES_VIEW_STATE + 
"=";
@@ -128,9 +123,6 @@ public class FormResubmitSupport {
     private static final Optional<String> RESUBMIT_HOST = 
Optional.ofNullable(System.getProperty(FORM_RESUBMIT_HOST));
     private static final Optional<Integer> RESUBMIT_PORT = 
Optional.ofNullable(System.getProperty(FORM_RESUBMIT_PORT))
             .map(Integer::valueOf);
-    private static final String FORM_RESUBMIT_WHITE_LIST_MAX_SIZE = 
"org.apache.shiro.form-resubmit-whitelist-max-size";
-    private static final Optional<Integer> RESUBMIT_WHITE_LIST_MAX_SIZE =
-            
Optional.ofNullable(System.getProperty(FORM_RESUBMIT_WHITE_LIST_MAX_SIZE)).map(Integer::valueOf);
     private static final String FORM_RESUBMIT_BLACK_LIST_MAX_SIZE = 
"org.apache.shiro.form-resubmit-blacklist-max-size";
     private static final Optional<Integer> RESUBMIT_BLACK_LIST_MAX_SIZE =
             
Optional.ofNullable(System.getProperty(FORM_RESUBMIT_BLACK_LIST_MAX_SIZE)).map(Integer::valueOf);
@@ -234,7 +226,7 @@ public class FormResubmitSupport {
                 if (rememberMeManager != null && 
rememberMeManager.getCipherService() != null) {
                     var cachedData = Optional.ofNullable((byte[]) 
cache.get(savedFormDataKey));
                     savedFormData = cachedData.map(encryptedData ->
-                            decrypt(encryptedData, 
rememberMeManager)).orElse(savedFormData);
+                            decrypt(encryptedData, 
rememberMeManager)).orElse(null);
                 } else {
                     savedFormData = (String) cache.get(savedFormDataKey);
                 }
@@ -270,7 +262,7 @@ public class FormResubmitSupport {
 
     static void saveRequest(HttpServletRequest request, HttpServletResponse 
response, boolean useReferer) {
         String path = useReferer ? getReferer(request)
-                : Servlets.getRequestURLWithQueryString(request);
+                : Servlets.getRequestURIWithQueryString(request);
         var rememberMeManager = getRememberMeManager();
         if (path != null && rememberMeManager != null) {
             Servlets.addResponseCookie(request, response, 
WebUtils.SAVED_REQUEST_KEY,
@@ -293,29 +285,34 @@ public class FormResubmitSupport {
     }
 
     static String getReferer(HttpServletRequest request) {
-        String referer = request.getHeader("referer");
-        if (referer == null || referer.isBlank()) {
+        return normalizeSavedRequest(request.getHeader("referer"), request);
+    }
+
+    static String normalizeSavedRequest(String savedRequest, 
HttpServletRequest request) {
+        if (savedRequest == null || savedRequest.isBlank()) {
             return null;
         }
-
         try {
-            URI uri = URI.create(referer);
-
-            String contextPath = WebUtils.getContextPath(request);
-            String path = WebUtils.normalize(uri.getPath());
-
-            if (path == null) {
+            URI uri = URI.create(savedRequest);
+            String rawPath = uri.getRawPath();
+            if (rawPath == null || !rawPath.startsWith("/")) {
+                // opaque URI (mailto:, javascript:), or relative / empty path
                 return null;
             }
-
+            String path = uri.getPath();
+            if (!path.equals(WebUtils.normalize(path))) {
+                // reject anything non-canonical: "//", "/./", "/../", and 
traversal
+                // above root (normalize returns null there, so equals() is 
false)
+                return null;
+            }
+            String contextPath = WebUtils.getContextPath(request);
             if (!contextPath.isEmpty()
                     && !path.equals(contextPath)
                     && !path.startsWith(contextPath + "/")) {
                 return null;
             }
-
             String query = uri.getRawQuery();
-            return query == null ? path : path + "?" + query;
+            return query == null ? rawPath : rawPath + "?" + query;
         } catch (IllegalArgumentException e) {
             return null;
         }
@@ -333,7 +330,8 @@ public class FormResubmitSupport {
     @SneakyThrows({IOException.class, InterruptedException.class})
     static void redirectToSaved(HttpServletRequest request, 
HttpServletResponse response,
             FallbackPredicate useFallbackPath, String fallbackPath, boolean 
resubmit) {
-        String savedRequest = decrypt(Servlets.getRequestCookie(request, 
WebUtils.SAVED_REQUEST_KEY), getRememberMeManager());
+        String savedRequest = 
normalizeSavedRequest(decrypt(Servlets.getRequestCookie(request, 
WebUtils.SAVED_REQUEST_KEY),
+                getRememberMeManager()), request);
         if (savedRequest != null) {
             doRedirectToSaved(request, response, savedRequest, resubmit);
         } else {
@@ -368,8 +366,8 @@ public class FormResubmitSupport {
             String formData = getSavedFormDataFromKey(savedFormDataKey, 
cache::set);
             try {
                 if (formData != null) {
-                    Optional.ofNullable(resubmitSavedForm(formData, 
savedFormDataKeyString, savedRequest,
-                                    request, response, 
request.getServletContext(), false, true))
+                    Optional.ofNullable(resubmitSavedForm(formData, 
savedRequest, request, response,
+                                    request.getServletContext(), false, true))
                             .ifPresent(path -> doFacesRedirect(request, 
response, path));
                     doRedirectAtEnd = false;
                 } else {
@@ -445,7 +443,7 @@ public class FormResubmitSupport {
         return loginUrl != null && 
request.getRequestURI().equals(request.getContextPath() + loginUrl);
     }
 
-    static String resubmitSavedForm(@NonNull String savedFormData, String 
savedFormDataKey, @NonNull String savedRequest,
+    static String resubmitSavedForm(@NonNull String savedFormData, @NonNull 
String rawSavedRequest,
             HttpServletRequest originalRequest, HttpServletResponse 
originalResponse,
             ServletContext servletContext, boolean rememberedAjaxResubmit, 
boolean redirect)
             throws InterruptedException, IOException {
@@ -461,11 +459,17 @@ public class FormResubmitSupport {
             originalResponse.setStatus(AUTHFAIL);
             return resubmitResponseCleanup(originalRequest);
         }
-        URI overriddenRequestURI = 
overrideSavedRequestURI(URI.create(savedRequest));
+        String savedRequest = normalizeSavedRequest(rawSavedRequest, 
originalRequest);
+        if (savedRequest == null) {
+            log.debug("Form resubmit: rejecting saved request");
+            return originalRequest.getContextPath();
+        }
+        URI overriddenRequestURI = overrideSavedRequestURI(
+                
URI.create(Servlets.getRequestBaseURL(originalRequest)).resolve(savedRequest));
         var cookieManager = new CookieManager();
         HttpClient client = 
HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(2))
                 .cookieHandler(cookieManager).build();
-        if (!checkWhitelist(servletContext, overriddenRequestURI, client, 
savedFormDataKey)) {
+        if (isBlacklisted(overriddenRequestURI.getAuthority(), 
servletContext)) {
             return savedRequest;
         }
         initializeCookies(overriddenRequestURI, servletContext, cookieManager, 
originalRequest);
@@ -476,9 +480,11 @@ public class FormResubmitSupport {
             HttpRequest postRequest = 
constructPostRequest(overriddenRequestURI, decodedFormData.result);
             response = sendResubmitRequest(client, postRequest);
         } catch (IOException e) {
-            log.warn("Unable to resubmit form to {}" + System.lineSeparator()
+            putBlacklistEntry(overriddenRequestURI.getAuthority(), 
servletContext);
+            log.warn("Unable to resubmit form to {}{}"
                     + "perhaps set org.apache.shiro.form-resubmit-host or "
-                    + "org.apache.shiro.form-resubmit-port system property?", 
overriddenRequestURI, e);
+                    + "org.apache.shiro.form-resubmit-port system property?",
+                    overriddenRequestURI, System.lineSeparator(), e);
             return savedRequest;
         }
         if (rememberedAjaxResubmit && !decodedFormData.isStatelessRequest) {
@@ -487,7 +493,7 @@ public class FormResubmitSupport {
             log.debug("Redirect request: {}, response: {}", redirectRequest, 
redirectResponse);
             return processResubmitResponse(redirectResponse, originalRequest, 
originalResponse,
                     response.headers(), savedRequest, servletContext,
-                    true, rememberedAjaxResubmit, redirect);
+                    true, true, redirect);
         } else {
             deleteCookie(originalResponse, servletContext, 
SHIRO_FORM_DATA_KEY);
             return processResubmitResponse(response, originalRequest, 
originalResponse,
@@ -629,83 +635,53 @@ public class FormResubmitSupport {
         response.setDateHeader(EXPIRES, 0);
     }
 
-    private static boolean checkWhitelist(ServletContext servletContext, URI 
savedRequestURI, HttpClient client,
-                                          String savedFormDataKey) {
-        if (!isSecurityManagerTypeOf(getSecurityManager(), 
DefaultSecurityManager.class)) {
-            log.warn("Shiro SecurityManager is not configured for form 
resubmit whitelist caching");
-            return false;
-        }
-        DefaultSecurityManager dsm = 
getSecurityManager(DefaultSecurityManager.class);
-        if (dsm.getCacheManager() == null) {
-            log.warn("Shiro Cache manager is not configured, cannot cache form 
resubmit whitelist state");
-            return false;
-        }
-
-        Cache<String, Boolean> whitelist = getWhitelistCache(dsm);
-        Cache<String, Long> blacklist = getBlacklistCache(dsm);
-        String authority = savedRequestURI.getAuthority();
-
-        if (Boolean.TRUE.equals(whitelist.get(authority))) {
-            return true;
-        } else if (isBlacklisted(blacklist, authority)) {
-            log.debug("Form resubmit blacklist cache hit for {}", 
savedRequestURI);
-            return false;
-        } else if (checkWhitelistClient(savedRequestURI, 
servletContext.getContextPath(), client,
-                savedFormDataKey, dsm.getCacheManager())) {
-            if (isFormResubmitWhitelistEnabled(servletContext)) {
-                putWhitelistEntry(whitelist, authority);
-            }
-            blacklist.remove(authority);
-            return true;
-        }
-
-        if (isFormResubmitBlacklistEnabled(servletContext)) {
-            putBlacklistEntry(blacklist, authority);
-        }
-        return false;
-    }
-
-    static Cache<String, Boolean> getWhitelistCache(DefaultSecurityManager 
securityManager) {
-        return 
securityManager.getCacheManager().getCache(FORM_RESUBMIT_WHITELIST);
-    }
-
     static Cache<String, Long> getBlacklistCache(DefaultSecurityManager 
securityManager) {
+        if (securityManager == null || securityManager.getCacheManager() == 
null) {
+            return null;
+        }
         return 
securityManager.getCacheManager().getCache(FORM_RESUBMIT_BLACKLIST);
     }
 
-    private static void putWhitelistEntry(Cache<String, Boolean> whitelist, 
String authority) {
-        if (whitelist.get(authority) == null) {
-            @SuppressWarnings("checkstyle:MagicNumber")
-            int maxSize = RESUBMIT_WHITE_LIST_MAX_SIZE.orElse(1000);
-            if (whitelist.size() >= maxSize) {
-                log.warn("Form resubmit whitelist exceeded max size of {}. 
Clearing whitelist.", maxSize);
-                whitelist.clear();
+    private static void putBlacklistEntry(String authority, ServletContext 
servletContext) {
+        var blacklist = getBlacklistCache(getDefaultSecurityManager());
+        if (blacklist != null && (servletContext == null || 
isFormResubmitBlacklistEnabled(servletContext))) {
+            if (blacklist.get(authority) == null) {
+                @SuppressWarnings("checkstyle:MagicNumber")
+                int maxSize = RESUBMIT_BLACK_LIST_MAX_SIZE.orElse(1000);
+                if (blacklist.size() >= maxSize) {
+                    log.warn("Form resubmit blacklist exceeded max size of {}. 
Clearing blacklist.", maxSize);
+                    blacklist.clear();
+                }
             }
+            blacklist.put(authority, System.currentTimeMillis());
         }
-        whitelist.put(authority, Boolean.TRUE);
     }
 
-    private static void putBlacklistEntry(Cache<String, Long> blacklist, 
String authority) {
-        if (blacklist.get(authority) == null) {
-            @SuppressWarnings("checkstyle:MagicNumber")
-            int maxSize = RESUBMIT_BLACK_LIST_MAX_SIZE.orElse(1000);
-            if (blacklist.size() >= maxSize) {
-                log.warn("Form resubmit blacklist exceeded max size of {}. 
Clearing blacklist.", maxSize);
-                blacklist.clear();
-            }
+    private static DefaultSecurityManager getDefaultSecurityManager() {
+        if (!isSecurityManagerTypeOf(getSecurityManager(), 
DefaultSecurityManager.class)) {
+            log.debug("Shiro SecurityManager is not configured for form 
resubmit blacklist caching");
+            return null;
         }
-        blacklist.put(authority, System.currentTimeMillis());
+        DefaultSecurityManager dsm = 
getSecurityManager(DefaultSecurityManager.class);
+        if (dsm.getCacheManager() == null) {
+            log.debug("Shiro Cache manager is not configured, cannot cache 
form resubmit blacklist state");
+            return null;
+        }
+        return dsm;
     }
 
-    static boolean isBlacklisted(Cache<String, Long> blacklist, String 
authority) {
+    static boolean isBlacklisted(String authority, ServletContext 
servletContext) {
         long currentTimeMillis = System.currentTimeMillis();
-        return isBlacklisted(blacklist, authority,
+        return isBlacklisted(getBlacklistCache(getDefaultSecurityManager()), 
servletContext, authority,
                 
Duration.ofSeconds(RESUBMIT_BLACK_LIST_TTL_SECONDS.orElse(DEFAULT_RESUBMIT_BLACK_LIST_TTL_SECONDS)),
                 currentTimeMillis);
     }
 
-    static boolean isBlacklisted(Cache<String, Long> blacklist, String 
authority,
+    static boolean isBlacklisted(Cache<String, Long> blacklist, ServletContext 
servletContext, String authority,
             Duration ttl, long currentTimeMillis) {
+        if (blacklist == null || (servletContext != null && 
!isFormResubmitBlacklistEnabled(servletContext))) {
+            return false;
+        }
         Long blacklistedAt = blacklist.get(authority);
         if (blacklistedAt == null) {
             return false;
@@ -718,53 +694,6 @@ public class FormResubmitSupport {
         return active;
     }
 
-    private static boolean checkWhitelistClient(URI savedRequestURI, String 
contextPath, HttpClient client,
-                                                String savedFormDataKey, 
@NonNull CacheManager cacheManager) {
-        Cache<UUID, String> cache = null;
-        UUID savedFormDataUUID = null;
-
-        try {
-            var rememberMeManager = getRememberMeManager();
-            if (rememberMeManager == null || 
rememberMeManager.getCipherService() == null
-                    || rememberMeManager.getSerializer() == null) {
-                log.warn("Form resubmit cipher service not available, unable 
to decrypt - resubmit will not be available.");
-                return false;
-            }
-
-            if (savedFormDataKey == null) {
-                savedFormDataUUID = UUID.randomUUID();
-                savedFormDataKey = savedFormDataUUID.toString();
-                cache = cacheManager.getCache(FORM_DATA_CACHE);
-                cache.put(savedFormDataUUID, 
"__DUMMY_FOR_CLIENT_WHITELIST_CHECK__");
-            }
-
-            var request = HttpRequest.newBuilder()
-                    
.uri(URI.create("%s://%s%s%s".formatted(savedRequestURI.getScheme(), 
savedRequestURI.getAuthority(),
-                            contextPath, FORM_RESUBMIT_CHECK_SERVLET_PATH)))
-                    .timeout(Duration.ofSeconds(3)).header(CONTENT_TYPE, 
"text/plain")
-                    
.POST(HttpRequest.BodyPublishers.ofString(rememberMeManager.getCipherService()
-                            .encrypt((FORM_DATA_KEY_PREFIX + 
savedFormDataKey).getBytes(StandardCharsets.UTF_8),
-                                    
rememberMeManager.getEncryptionCipherKey()).toBase64())).build();
-            var response = client.send(request, 
HttpResponse.BodyHandlers.ofString());
-
-            if (response.statusCode() == OK && 
Objects.equals(decrypt(response.body(), rememberMeManager), savedFormDataKey)) {
-                log.debug("Form resubmit whitelist check succeeded for {}", 
savedRequestURI);
-                return true;
-            } else {
-                log.debug("Form resubmit whitelist check failed for {} with 
status code {}",
-                        savedRequestURI, response.statusCode());
-            }
-        } catch (IOException | InterruptedException e) {
-            log.debug("Form resubmit whitelist check failed for {} with 
exception: {}",
-                    savedRequestURI, e);
-        } finally {
-            if (cache != null) {
-                cache.remove(savedFormDataUUID);
-            }
-        }
-        return false;
-    }
-
     public static DefaultWebSessionManager 
getNativeSessionManager(SecurityManager securityManager) {
         DefaultWebSessionManager rv = null;
         SecurityManager unwrapped = unwrapSecurityManager(securityManager, 
SecurityManager.class, type -> false);
diff --git 
a/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitValidator.java
 
b/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitValidator.java
deleted file mode 100644
index 07cade479..000000000
--- 
a/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitValidator.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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.ee.filters;
-
-import jakarta.servlet.annotation.WebServlet;
-import jakarta.servlet.http.HttpServlet;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.shiro.mgt.DefaultSecurityManager;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.Optional;
-import java.util.UUID;
-import java.util.stream.Collectors;
-import static org.apache.shiro.SecurityUtils.getSecurityManager;
-import static org.apache.shiro.ee.filters.FormResubmitSupport.FORM_DATA_CACHE;
-import static 
org.apache.shiro.ee.filters.FormResubmitSupport.FORM_DATA_KEY_PREFIX;
-import static org.apache.shiro.ee.filters.FormResubmitSupport.decrypt;
-import static 
org.apache.shiro.ee.filters.FormResubmitSupport.getRememberMeManager;
-import static 
org.apache.shiro.web.filter.authc.NoAccessFilter.FORM_RESUBMIT_CHECK_SERVLET_PATH;
-
-@Slf4j
-@WebServlet(name = "ShiroFormResubmitValidator", urlPatterns = 
FORM_RESUBMIT_CHECK_SERVLET_PATH)
-public class FormResubmitValidator extends HttpServlet {
-    @Override
-    protected void doPost(HttpServletRequest request, HttpServletResponse 
response) {
-        var rememberMeManager = getRememberMeManager();
-        if (rememberMeManager == null || rememberMeManager.getCipherService() 
== null) {
-            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
-        } else {
-            try {
-                String formDataKey = 
decrypt(request.getReader().lines().collect(Collectors.joining()), 
rememberMeManager)
-                        .substring(FORM_DATA_KEY_PREFIX.length());
-                var cache = getSecurityManager(DefaultSecurityManager.class)
-                        .getCacheManager().getCache(FORM_DATA_CACHE);
-                
Optional.ofNullable(cache.get(UUID.fromString(formDataKey))).orElseThrow(IllegalCallerException::new);
-                String encryptedFormDataKey = 
rememberMeManager.getCipherService()
-                        .encrypt(formDataKey.getBytes(StandardCharsets.UTF_8),
-                        rememberMeManager.getEncryptionCipherKey()).toBase64();
-                response.getWriter().write(encryptedFormDataKey);
-                response.setStatus(HttpServletResponse.SC_OK);
-            } catch (IOException | IllegalCallerException e) {
-                log.warn("Form resubmit verification: invalid input or failed 
to write encrypted session id to response", e);
-                response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
-            }
-        }
-    }
-}
diff --git 
a/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/ShiroFilter.java 
b/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/ShiroFilter.java
index 9413d1261..0576b3d99 100644
--- 
a/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/ShiroFilter.java
+++ 
b/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/ShiroFilter.java
@@ -245,8 +245,8 @@ public class ShiroFilter extends 
org.apache.shiro.web.servlet.ShiroFilter {
             log.debug("Resubmitting Post Data: {}", postData);
             var httpRequest = WebUtils.toHttp(request);
             boolean rememberedAjaxResubmit = 
"partial/ajax".equals(httpRequest.getHeader("Faces-Request"));
-            Optional.ofNullable(resubmitSavedForm(postData, null,
-                    Servlets.getRequestURLWithQueryString(httpRequest),
+            Optional.ofNullable(resubmitSavedForm(postData,
+                    Servlets.getRequestURIWithQueryString(httpRequest),
                     WebUtils.toHttp(request), WebUtils.toHttp(response),
                     request.getServletContext(), rememberedAjaxResubmit, 
false))
                     .ifPresent(url -> sendRedirect(response, url));
diff --git 
a/support/jakarta-ee/src/main/java/org/apache/shiro/ee/listeners/EnvironmentLoaderListener.java
 
b/support/jakarta-ee/src/main/java/org/apache/shiro/ee/listeners/EnvironmentLoaderListener.java
index 5d49862ec..cc47fade8 100644
--- 
a/support/jakarta-ee/src/main/java/org/apache/shiro/ee/listeners/EnvironmentLoaderListener.java
+++ 
b/support/jakarta-ee/src/main/java/org/apache/shiro/ee/listeners/EnvironmentLoaderListener.java
@@ -46,7 +46,6 @@ public class EnvironmentLoaderListener extends 
EnvironmentLoader implements Serv
     private static final String SHIRO_EE_CHAR_ENCODING_PARAM = 
"org.apache.shiro.ee.character-encoding";
     private static final String FORM_RESUBMIT_DISABLED_PARAM = 
"org.apache.shiro.form-resubmit.disabled";
     private static final String FORM_RESUBMIT_SECURE_COOKIES = 
"org.apache.shiro.form-resubmit.secure-cookies";
-    private static final String FORM_RESUBMIT_WHITE_LIST_DISABLED = 
"org.apache.shiro.form-resubmit.whitelist.disabled";
     private static final String FORM_RESUBMIT_BLACK_LIST_DISABLED = 
"org.apache.shiro.form-resubmit.blacklist.disabled";
     private static final String SHIRO_WEB_DISABLE_PRINCIPAL_PARAM = 
"org.apache.shiro.web.disable-principal";
 
@@ -66,10 +65,6 @@ public class EnvironmentLoaderListener extends 
EnvironmentLoader implements Serv
         return 
Boolean.TRUE.equals(ctx.getAttribute(FORM_RESUBMIT_SECURE_COOKIES));
     }
 
-    public static boolean isFormResubmitWhitelistEnabled(ServletContext ctx) {
-        return 
!Boolean.TRUE.equals(ctx.getAttribute(FORM_RESUBMIT_WHITE_LIST_DISABLED));
-    }
-
     public static boolean isFormResubmitBlacklistEnabled(ServletContext ctx) {
         return 
!Boolean.TRUE.equals(ctx.getAttribute(FORM_RESUBMIT_BLACK_LIST_DISABLED));
     }
@@ -106,9 +101,6 @@ public class EnvironmentLoaderListener extends 
EnvironmentLoader implements Serv
         } else {
             sce.getServletContext().setAttribute(FORM_RESUBMIT_SECURE_COOKIES, 
Boolean.FALSE);
         }
-        if 
(Boolean.parseBoolean(sce.getServletContext().getInitParameter(FORM_RESUBMIT_WHITE_LIST_DISABLED)))
 {
-            
sce.getServletContext().setAttribute(FORM_RESUBMIT_WHITE_LIST_DISABLED, 
Boolean.TRUE);
-        }
         if 
(Boolean.parseBoolean(sce.getServletContext().getInitParameter(FORM_RESUBMIT_BLACK_LIST_DISABLED)))
 {
             
sce.getServletContext().setAttribute(FORM_RESUBMIT_BLACK_LIST_DISABLED, 
Boolean.TRUE);
         }
diff --git 
a/support/jakarta-ee/src/test/java/org/apache/shiro/ee/filters/FormSupportTest.java
 
b/support/jakarta-ee/src/test/java/org/apache/shiro/ee/filters/FormSupportTest.java
index 36ab39bf5..afb1d2d2d 100644
--- 
a/support/jakarta-ee/src/test/java/org/apache/shiro/ee/filters/FormSupportTest.java
+++ 
b/support/jakarta-ee/src/test/java/org/apache/shiro/ee/filters/FormSupportTest.java
@@ -13,6 +13,7 @@
  */
 package org.apache.shiro.ee.filters;
 
+import jakarta.servlet.ServletContext;
 import org.apache.shiro.ee.filters.FormResubmitSupport.PartialAjaxResult;
 import org.apache.shiro.cache.MemoryConstrainedCacheManager;
 
@@ -48,12 +49,15 @@ import org.apache.shiro.mgt.DefaultSecurityManager;
  * Resubmit forms support
  */
 @ExtendWith(MockitoExtension.class)
+@SuppressWarnings("checkstyle:MethodCount")
 class FormSupportTest {
     private static final long BLACKLISTED_AT = 1_000L;
     private static final Duration BLACKLIST_TTL = Duration.ofSeconds(60);
 
     @Mock
     private HttpServletRequest request;
+    @Mock
+    private ServletContext servletContext;
 
     @Test
     void nullReferer() {
@@ -70,7 +74,6 @@ class FormSupportTest {
     @Test
     void plainStringReferer() {
         when(request.getHeader("referer")).thenReturn("hello");
-        when(request.getContextPath()).thenReturn("/myapp");
         assertThat(getReferer(request)).isNull();
     }
 
@@ -129,34 +132,26 @@ class FormSupportTest {
     }
 
     @Test
-    void normalizedPathWithinContextIsAccepted() {
+    void nonCanonicalPathIsRejected() {
         
when(request.getHeader("referer")).thenReturn("https://example.com/myapp//foo/./bar.xhtml";);
-        when(request.getContextPath()).thenReturn("/myapp");
-
-        assertThat(getReferer(request)).isEqualTo("/myapp/foo/bar.xhtml");
+        assertThat(getReferer(request)).isNull();
     }
 
     @Test
     void normalizedPathEscapingContextIsRejected() {
         
when(request.getHeader("referer")).thenReturn("https://example.com/myapp/../otherapp/page.xhtml";);
-        when(request.getContextPath()).thenReturn("/myapp");
-
         assertThat(getReferer(request)).isNull();
     }
 
     @Test
     void opaqueUriRefererIsRejected() {
         
when(request.getHeader("referer")).thenReturn("mailto:[email protected]";);
-        when(request.getContextPath()).thenReturn("/myapp");
-
         assertThat(getReferer(request)).isNull();
     }
 
     @Test
     void javascriptUriRefererIsRejected() {
         when(request.getHeader("referer")).thenReturn("javascript:alert(1)");
-        when(request.getContextPath()).thenReturn("/myapp");
-
         assertThat(getReferer(request)).isNull();
     }
 
@@ -177,7 +172,7 @@ class FormSupportTest {
     }
 
     @Test
-    void externalHostWithMatchingContextCurrentlyPasses() {
+    void externalHostIsStrippedToPath() {
         
when(request.getHeader("referer")).thenReturn("https://attacker.example/myapp/login.xhtml";);
         when(request.getContextPath()).thenReturn("/myapp");
 
@@ -186,19 +181,25 @@ class FormSupportTest {
 
     @Test
     void encodedPathTraversalRefererIsRejected() {
-        when(request.getHeader("referer"))
-                
.thenReturn("https://example.com/myapp/%2e%2e/otherapp/page.xhtml";);
-        when(request.getContextPath()).thenReturn("/myapp");
-
+        
when(request.getHeader("referer")).thenReturn("https://example.com/myapp/%2e%2e/otherapp/page.xhtml";);
         assertThat(getReferer(request)).isNull();
     }
 
     @Test
     void encodedPathTraversalWithEncodedSlashesRefererIsRejected() {
-        when(request.getHeader("referer"))
-                
.thenReturn("https://example.com/myapp/%2e%2e%2fotherapp%2fpage.xhtml";);
-        when(request.getContextPath()).thenReturn("/myapp");
+        
when(request.getHeader("referer")).thenReturn("https://example.com/myapp/%2e%2e%2fotherapp%2fpage.xhtml";);
+        assertThat(getReferer(request)).isNull();
+    }
 
+    @Test
+    void doubleSlashPathWithRootContextIsRejected() {
+        
when(request.getHeader("referer")).thenReturn("https://example.com//evil.com/x";);
+        assertThat(getReferer(request)).isNull();
+    }
+
+    @Test
+    void doubleSlashPathWithinContextIsRejected() {
+        
when(request.getHeader("referer")).thenReturn("https://attacker.example//myapp/x";);
         assertThat(getReferer(request)).isNull();
     }
 
@@ -348,18 +349,15 @@ class FormSupportTest {
 
     @Test
     @SuppressWarnings("checkstyle:MagicNumber")
-    void whitelistAndBlacklistUseShiroCacheManager() {
+    void blacklistUseShiroCacheManager() {
         var securityManager = new DefaultSecurityManager();
         securityManager.setCacheManager(new MemoryConstrainedCacheManager());
 
-        var whitelist = FormResubmitSupport.getWhitelistCache(securityManager);
         var blacklist = FormResubmitSupport.getBlacklistCache(securityManager);
 
-        whitelist.put("good.example", Boolean.TRUE);
         blacklist.put("bad.example", BLACKLISTED_AT);
 
-        
assertThat(FormResubmitSupport.getWhitelistCache(securityManager).get("good.example")).isTrue();
-        assertThat(FormResubmitSupport.isBlacklisted(blacklist, "bad.example",
+        assertThat(FormResubmitSupport.isBlacklisted(blacklist, null, 
"bad.example",
                 BLACKLIST_TTL, 1_500L)).isTrue();
     }
 
@@ -372,11 +370,28 @@ class FormSupportTest {
         var blacklist = FormResubmitSupport.getBlacklistCache(securityManager);
         blacklist.put("expired.example", BLACKLISTED_AT);
 
-        assertThat(FormResubmitSupport.isBlacklisted(blacklist, 
"expired.example",
+        assertThat(FormResubmitSupport.isBlacklisted(blacklist, null, 
"expired.example",
                 BLACKLIST_TTL, 61_001L)).isFalse();
         assertThat(blacklist.get("expired.example")).isNull();
     }
 
+    @Test
+    @SuppressWarnings("checkstyle:MagicNumber")
+    void blacklistHonoursEnabledFlag() {
+        var securityManager = new DefaultSecurityManager();
+        securityManager.setCacheManager(new MemoryConstrainedCacheManager());
+        var blacklist = FormResubmitSupport.getBlacklistCache(securityManager);
+        blacklist.put("bad.example", BLACKLISTED_AT);
+
+        // attribute absent → enabled
+        assertThat(FormResubmitSupport.isBlacklisted(blacklist, 
servletContext, "bad.example",
+                BLACKLIST_TTL, 1_500L)).isTrue();
+
+        
when(servletContext.getAttribute("org.apache.shiro.form-resubmit.blacklist.disabled")).thenReturn(Boolean.TRUE);
+        assertThat(FormResubmitSupport.isBlacklisted(blacklist, 
servletContext, "bad.example",
+                BLACKLIST_TTL, 1_500L)).isFalse();
+    }
+
     private static String decode(String plain) {
         return URLDecoder.decode(plain, StandardCharsets.UTF_8);
     }
diff --git 
a/web/src/main/java/org/apache/shiro/web/filter/authc/NoAccessFilter.java 
b/web/src/main/java/org/apache/shiro/web/filter/authc/NoAccessFilter.java
index 4b2614234..e3380164f 100644
--- a/web/src/main/java/org/apache/shiro/web/filter/authc/NoAccessFilter.java
+++ b/web/src/main/java/org/apache/shiro/web/filter/authc/NoAccessFilter.java
@@ -18,7 +18,6 @@
  */
 package org.apache.shiro.web.filter.authc;
 
-import jakarta.servlet.http.HttpServletRequest;
 import org.apache.shiro.authc.AuthenticationToken;
 import org.apache.shiro.web.util.WebUtils;
 import org.slf4j.Logger;
@@ -32,8 +31,6 @@ import jakarta.servlet.ServletResponse;
  * that do not match existing filter patterns.
  */
 public class NoAccessFilter extends AuthenticatingFilter {
-    public static final String FORM_RESUBMIT_CHECK_SERVLET_PATH = 
"/org.apache.shiro.form-resubmit-check";
-
     private final Logger log = LoggerFactory.getLogger(NoAccessFilter.class);
 
     @Override
@@ -47,14 +44,4 @@ public class NoAccessFilter extends AuthenticatingFilter {
     protected AuthenticationToken createToken(ServletRequest request, 
ServletResponse response) throws Exception {
         return null;
     }
-
-    @Override
-    protected boolean isAccessAllowed(ServletRequest request, ServletResponse 
response, Object mappedValue) {
-        if (request instanceof HttpServletRequest) {
-            HttpServletRequest httpRequest = WebUtils.toHttp(request);
-            return httpRequest.getMethod().equals("POST")
-                    && 
httpRequest.getServletPath().equals(FORM_RESUBMIT_CHECK_SERVLET_PATH);
-        }
-        return false;
-    }
 }

Reply via email to