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 fd1b1a528 Bugfix: improve form resubmit handling (#2858)
fd1b1a528 is described below
commit fd1b1a528565045f7c428e66085bfffbe4b70392
Author: Lenny Primak <[email protected]>
AuthorDate: Sun Aug 16 12:45:24 2026 -0500
Bugfix: improve form resubmit handling (#2858)
---
src/owasp-suppression.xml | 17 +-
.../shiro/ee/filters/FormResubmitSupport.java | 202 +++++++++++++++------
.../ee/filters/FormResubmitSupportCookies.java | 32 ++++
.../shiro/ee/filters/FormResubmitValidator.java | 30 +--
.../org/apache/shiro/ee/filters/ShiroFilter.java | 4 +-
.../shiro/web/filter/authc/NoAccessFilter.java | 2 +-
.../web/servlet/ShiroHttpServletResponse.java | 30 +--
7 files changed, 215 insertions(+), 102 deletions(-)
diff --git a/src/owasp-suppression.xml b/src/owasp-suppression.xml
index 03ae97d0c..6b0ed1cab 100644
--- a/src/owasp-suppression.xml
+++ b/src/owasp-suppression.xml
@@ -19,16 +19,11 @@
-->
<suppressions
xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.1.xsd">
- <suppress>
- <notes><![CDATA[ file name: tomcat-embed-core-8.5.5.jar ]]></notes>
- <sha1>d55e12a418ff99ecd723a118c2a28bb91079972d</sha1>
- <cpe>cpe:/a:apache:tomcat:8.5.5</cpe>
- </suppress>
-
- <suppress>
- <notes><![CDATA[ file name: tomcat-embed-websocket-8.5.5.jar
]]></notes>
- <sha1>fd99cd1cd4c824abdf03466f0509f067747f0d1a</sha1>
- <cpe>cpe:/a:apache:tomcat:8.5.5</cpe>
- </suppress>
+<!-- EXAMPLE-->
+<!-- <suppress>-->
+<!-- <notes><![CDATA[ file name: tomcat-embed-core-8.5.5.jar
]]></notes>-->
+<!-- <sha1>d55e12a418ff99ecd723a118c2a28bb91079972d</sha1>-->
+<!-- <cpe>cpe:/a:apache:tomcat:8.5.5</cpe>-->
+<!-- </suppress>-->
</suppressions>
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 1aa637d80..44f1cf56d 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
@@ -39,11 +39,11 @@ import java.time.Duration;
import java.util.Collections;
import org.apache.shiro.crypto.CryptoException;
import org.apache.shiro.ee.filters.Forms.FallbackPredicate;
+import static
org.apache.shiro.ee.filters.FormResubmitSupportCookies.initializeCookies;
import static
org.apache.shiro.ee.filters.FormResubmitSupportCookies.transformCookieHeader;
import static
org.apache.shiro.ee.listeners.EnvironmentLoaderListener.isFormResubmitDisabled;
import java.io.IOException;
import java.net.CookieManager;
-import java.net.HttpCookie;
import java.net.URI;
import java.net.URLDecoder;
import java.net.http.HttpClient;
@@ -51,18 +51,24 @@ import java.net.http.HttpHeaders;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
+import java.util.List;
import java.util.Objects;
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.IniEnvironment.hasFacesContext;
import static
org.apache.shiro.web.filter.authc.NoAccessFilter.FORM_RESUBMIT_CHECK_SERVLET_PATH;
-import static
org.apache.shiro.web.mgt.CookieRememberMeManager.DEFAULT_REMEMBER_ME_COOKIE_NAME;
+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;
+import static org.apache.shiro.web.filter.authz.SslFilter.HTTPS_SCHEME;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletRequest;
-import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AccessLevel;
@@ -73,7 +79,6 @@ import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
-import org.apache.shiro.SecurityUtils;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.lang.codec.Base64;
import org.apache.shiro.mgt.AbstractRememberMeManager;
@@ -86,6 +91,7 @@ import org.jsoup.Jsoup;
import org.jsoup.select.Elements;
import org.omnifaces.util.Faces;
import org.omnifaces.util.Servlets;
+import org.owasp.encoder.Encode;
/**
* supporting methods for {@link Forms}
@@ -99,20 +105,20 @@ public class FormResubmitSupport {
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";
// encoded view state
private static final String FACES_VIEW_STATE = "jakarta.faces.ViewState";
private static final String FACES_VIEW_STATE_EQUALS = FACES_VIEW_STATE +
"=";
private static final Pattern VIEW_STATE_PATTERN
- =
Pattern.compile(String.format("(.*)(%s[-]?[\\d]+:[-]?[\\d]+)(.*)",
FACES_VIEW_STATE_EQUALS));
+ = Pattern.compile(String.format("(.*)(%s-?\\d+:-?\\d+)(.*)",
FACES_VIEW_STATE_EQUALS));
private static final String FACES_SOURCE = "jakarta.faces.source";
private static final String FACES_SOURCE_EQUALS = FACES_SOURCE + "=";
static final Pattern FACES_SOURCE_PATTERN
- = Pattern.compile(String.format("[\\&]?%s([\\w\\s:%%\\d]*)(.*)",
FACES_SOURCE_EQUALS));
+ = Pattern.compile(String.format("&?%s([\\w\\s:%%d]*)(.*)",
FACES_SOURCE_EQUALS));
private static final Pattern PARTIAL_REQUEST_PATTERN
- =
Pattern.compile("[\\&]?(%s.\\w+|%s.\\w+|%s)=[\\w\\s:%%\\d]*".formatted(
+ = Pattern.compile("&?(%s.\\w+|%s.\\w+|%s)=[\\w\\s:%%d]*".formatted(
"jakarta.faces.partial", "jakarta.faces.behavior", FACES_SOURCE));
- private static final Pattern INITIAL_AMPERSAND = Pattern.compile("^\\&");
- private static final String FORM_DATA_CACHE =
"org.apache.shiro.form-data-cache";
+ private static final Pattern INITIAL_AMPERSAND = Pattern.compile("^&");
private static final String FORM_RESUBMIT_HOST =
"org.apache.shiro.form-resubmit-host";
private static final String FORM_RESUBMIT_PORT =
"org.apache.shiro.form-resubmit-port";
private static final Optional<String> RESUBMIT_HOST =
Optional.ofNullable(System.getProperty(FORM_RESUBMIT_HOST));
@@ -129,6 +135,17 @@ public class FormResubmitSupport {
private static final Optional<Long> RESUBMIT_BLACK_LIST_TTL_SECONDS =
Optional.ofNullable(System.getProperty(FORM_RESUBMIT_BLACK_LIST_TTL_SECONDS)).map(Long::valueOf);
private static final long DEFAULT_RESUBMIT_BLACK_LIST_TTL_SECONDS = 60L;
+ private static final String SEC_FETCH_SITE = "Sec-Fetch-Site";
+ private static final String ORIGIN = "Origin";
+ private static final String CACHE_CONTROL = "Cache-Control";
+ private static final String NO_STORE = "no-store";
+ private static final String PRAGMA = "Pragma";
+ private static final String EXPIRES = "Expires";
+ private static final String NO_CACHE = "no-cache";
+ private static final Set<String> SECURITY_HEADERS =
+ Set.of("Content-Security-Policy",
"Content-Security-Policy-Report-Only",
+ "X-Content-Type-Options", "Referrer-Policy",
"X-Frame-Options",
+ "Cross-Origin-Opener-Policy", "Strict-Transport-Security");
static class HttpMethod {
static final String GET = "GET";
@@ -164,7 +181,7 @@ public class FormResubmitSupport {
static void savePostDataForResubmit(HttpServletRequest request,
HttpServletResponse response, @NonNull String loginUrl) {
if (isPostRequest(request) &&
isSecurityManagerTypeOf(getSecurityManager(),
- DefaultSecurityManager.class)) {
+ DefaultSecurityManager.class) && shouldSavePostData(request)) {
String postData = getPostData(request);
var cacheKey = UUID.randomUUID();
DefaultSecurityManager dsm =
getSecurityManager(DefaultSecurityManager.class);
@@ -203,22 +220,21 @@ public class FormResubmitSupport {
return request.getReader().lines().collect(Collectors.joining());
}
- static String getSavedFormDataFromKey(@NonNull String savedFormDataKey) {
+ static String getSavedFormDataFromKey(@NonNull UUID savedFormDataKey,
Consumer<Cache<Object, ?>> cacheConsumer) {
String savedFormData = null;
if (isSecurityManagerTypeOf(getSecurityManager(),
DefaultSecurityManager.class)) {
DefaultSecurityManager dsm =
getSecurityManager(DefaultSecurityManager.class);
if (dsm.getCacheManager() != null) {
var cache = dsm.getCacheManager().getCache(FORM_DATA_CACHE);
- var cacheKey = UUID.fromString(savedFormDataKey);
var rememberMeManager = getRememberMeManager();
if (rememberMeManager != null &&
rememberMeManager.getCipherService() != null) {
- var cachedData = Optional.ofNullable((byte[])
cache.get(cacheKey));
+ var cachedData = Optional.ofNullable((byte[])
cache.get(savedFormDataKey));
savedFormData = cachedData.map(encryptedData ->
decrypt(encryptedData,
rememberMeManager)).orElse(savedFormData);
} else {
- savedFormData = (String) cache.get(cacheKey);
+ savedFormData = (String) cache.get(savedFormDataKey);
}
- cache.remove(cacheKey);
+ cacheConsumer.accept(cache);
}
}
return savedFormData;
@@ -340,17 +356,25 @@ public class FormResubmitSupport {
private static void doRedirectToSaved(HttpServletRequest request,
HttpServletResponse response,
@NonNull String savedRequest, boolean resubmit) throws
IOException, InterruptedException {
deleteCookie(response, request.getServletContext(),
WebUtils.SAVED_REQUEST_KEY);
- String savedFormDataKey = Servlets.getRequestCookie(request,
SHIRO_FORM_DATA_KEY);
+ String savedFormDataKeyString = Servlets.getRequestCookie(request,
SHIRO_FORM_DATA_KEY);
boolean doRedirectAtEnd = true;
- if (savedFormDataKey != null && resubmit) {
- String formData = getSavedFormDataFromKey(savedFormDataKey);
- if (formData != null) {
- Optional.ofNullable(resubmitSavedForm(formData, savedRequest,
- request, response, request.getServletContext(), false,
true))
- .ifPresent(path -> doFacesRedirect(request, response,
path));
- doRedirectAtEnd = false;
- } else {
- deleteCookie(response, request.getServletContext(),
SHIRO_FORM_DATA_KEY);
+ if (savedFormDataKeyString != null && resubmit) {
+ AtomicReference<Cache<Object, ?>> cache = new AtomicReference<>();
+ UUID savedFormDataKey = UUID.fromString(savedFormDataKeyString);
+ String formData = getSavedFormDataFromKey(savedFormDataKey,
cache::set);
+ try {
+ if (formData != null) {
+ Optional.ofNullable(resubmitSavedForm(formData,
savedFormDataKeyString, savedRequest,
+ request, response,
request.getServletContext(), false, true))
+ .ifPresent(path -> doFacesRedirect(request,
response, path));
+ doRedirectAtEnd = false;
+ } else {
+ deleteCookie(response, request.getServletContext(),
SHIRO_FORM_DATA_KEY);
+ }
+ } finally {
+ if (cache.get() != null) {
+ cache.get().remove(savedFormDataKey);
+ }
}
}
if (doRedirectAtEnd) {
@@ -417,7 +441,7 @@ public class FormResubmitSupport {
return loginUrl != null &&
request.getRequestURI().equals(request.getContextPath() + loginUrl);
}
- static String resubmitSavedForm(@NonNull String savedFormData, @NonNull
String savedRequest,
+ static String resubmitSavedForm(@NonNull String savedFormData, String
savedFormDataKey, @NonNull String savedRequest,
HttpServletRequest originalRequest, HttpServletResponse
originalResponse,
ServletContext servletContext, boolean rememberedAjaxResubmit,
boolean redirect)
throws InterruptedException, IOException {
@@ -429,14 +453,18 @@ public class FormResubmitSupport {
}
if
(Boolean.TRUE.toString().equals(originalRequest.getHeader(FORM_IS_RESUBMITTED)))
{
log.debug("Form resubmit: internal auth failure");
+ setNoStoreHeaders(originalResponse);
originalResponse.setStatus(AUTHFAIL);
return resubmitResponseCleanup(originalRequest);
}
URI overriddenRequestURI =
overrideSavedRequestURI(URI.create(savedRequest));
- HttpClient client = buildHttpClient(overriddenRequestURI,
servletContext, originalRequest);
- if (!checkWhitelist(servletContext, overriddenRequestURI, client)) {
+ var cookieManager = new CookieManager();
+ HttpClient client =
HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(2))
+ .cookieHandler(cookieManager).build();
+ if (!checkWhitelist(servletContext, overriddenRequestURI, client,
savedFormDataKey)) {
return savedRequest;
}
+ initializeCookies(overriddenRequestURI, servletContext, cookieManager,
originalRequest);
HttpResponse<String> response;
PartialAjaxResult decodedFormData;
try {
@@ -535,6 +563,7 @@ public class FormResubmitSupport {
originalResponse.setHeader(LOCATION,
response.headers().firstValue(LOCATION).orElseThrow());
}
case OK:
+ propagateCacheHeaders(response, originalResponse);
// do not duplicate the session cookie(s)
transformCookieHeader(headers.allValues(SET_COOKIE))
.entrySet().stream().filter(not(entry -> entry.getKey()
@@ -546,8 +575,9 @@ public class FormResubmitSupport {
originalResponse.setCharacterEncoding(StandardCharsets.UTF_8.name());
originalResponse.getWriter().append(String.format(
"<partial-response><redirect
url=\"%s\"></redirect></partial-response>",
- savedRequest));
+ Encode.forXmlAttribute(savedRequest)));
} else {
+
response.headers().firstValue(CONTENT_TYPE).ifPresent(originalResponse::setContentType);
originalResponse.getWriter().append(response.body());
}
return resubmitResponseCleanup(originalRequest);
@@ -564,35 +594,39 @@ public class FormResubmitSupport {
return null;
}
- private static HttpClient buildHttpClient(URI savedRequest, ServletContext
servletContext,
- HttpServletRequest originalRequest) {
- CookieManager cookieManager = new CookieManager();
- var session = SecurityUtils.getSubject().getSession();
- var sessionCookieName = getSessionCookieName(servletContext,
getSecurityManager());
- var sessionCookie = new HttpCookie(sessionCookieName,
session.getId().toString());
- sessionCookie.setPath(servletContext.getContextPath());
- sessionCookie.setVersion(0);
- cookieManager.getCookieStore().add(savedRequest, sessionCookie);
- log.debug("Setting Cookie {}", sessionCookieName);
- for (Cookie origCookie : originalRequest.getCookies()) {
- if (!origCookie.getName().startsWith(sessionCookieName)
- &&
!origCookie.getName().equals(DEFAULT_REMEMBER_ME_COOKIE_NAME)) {
- try {
- log.debug("Setting Cookie {}", origCookie.getName());
- HttpCookie cookie = new HttpCookie(origCookie.getName(),
origCookie.getValue());
- cookie.setPath(servletContext.getContextPath());
- cookie.setVersion(0);
- cookieManager.getCookieStore().add(savedRequest, cookie);
- } catch (IllegalArgumentException e) {
- log.warn("Form Resubmit: Ignoring invalid cookie [{} -
{}]",
- origCookie.getName(), origCookie.getValue(), e);
- }
- }
+ private static void propagateCacheHeaders(HttpResponse<String> response,
HttpServletResponse originalResponse) {
+ HttpHeaders upstreamHeaders = response.headers();
+
+ List<String> cacheControlValues =
upstreamHeaders.allValues(CACHE_CONTROL);
+ originalResponse.setHeader(CACHE_CONTROL, cacheControlValues.isEmpty()
+ ? NO_STORE : String.join(", ", cacheControlValues));
+
+ List<String> pragmaValues = upstreamHeaders.allValues(PRAGMA);
+ originalResponse.setHeader(PRAGMA, pragmaValues.isEmpty()
+ ? NO_CACHE : String.join(", ", pragmaValues));
+
+ List<String> expiresValues = upstreamHeaders.allValues(EXPIRES);
+ if (expiresValues.isEmpty()) {
+ originalResponse.setDateHeader(EXPIRES, 0);
+ } else {
+ originalResponse.setHeader(EXPIRES,
expiresValues.get(expiresValues.size() - 1));
}
- return
HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(2)).cookieHandler(cookieManager).build();
+
+ upstreamHeaders.map().forEach((name, values) -> {
+ if (SECURITY_HEADERS.stream().anyMatch(name::equalsIgnoreCase)) {
+ values.forEach(v -> originalResponse.addHeader(name, v));
+ }
+ });
}
- private static boolean checkWhitelist(ServletContext servletContext, URI
savedRequestURI, HttpClient client) {
+ private static void setNoStoreHeaders(HttpServletResponse response) {
+ response.setHeader(CACHE_CONTROL, NO_STORE);
+ response.setHeader(PRAGMA, NO_CACHE);
+ 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;
@@ -612,7 +646,7 @@ public class FormResubmitSupport {
} else if (isBlacklisted(blacklist, authority)) {
log.debug("Form resubmit blacklist cache hit for {}",
savedRequestURI);
return false;
- } else if (checkWhitelistClient(savedRequestURI,
servletContext.getContextPath(), client)) {
+ } else if (checkWhitelistClient(savedRequestURI,
servletContext.getContextPath(), client, savedFormDataKey)) {
putWhitelistEntry(whitelist, authority);
blacklist.remove(authority);
return true;
@@ -675,7 +709,8 @@ public class FormResubmitSupport {
return active;
}
- private static boolean checkWhitelistClient(URI savedRequestURI, String
contextPath, HttpClient client) {
+ private static boolean checkWhitelistClient(URI savedRequestURI, String
contextPath, HttpClient client,
+ String savedFormDataKey) {
try {
var rememberMeManager = getRememberMeManager();
if (rememberMeManager == null ||
rememberMeManager.getCipherService() == null
@@ -687,11 +722,13 @@ public class FormResubmitSupport {
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)).GET().build();
+ .timeout(Duration.ofSeconds(3)).header(CONTENT_TYPE,
"text/plain")
+
.POST(HttpRequest.BodyPublishers.ofString(rememberMeManager.getCipherService()
+
.encrypt(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),
-
SecurityUtils.getSubject().getSession().getId().toString())) {
+ if (response.statusCode() == OK &&
Objects.equals(decrypt(response.body(), rememberMeManager), savedFormDataKey)) {
log.debug("Form resubmit whitelist check succeeded for {}",
savedRequestURI);
return true;
} else {
@@ -776,4 +813,49 @@ public class FormResubmitSupport {
return STATE_SAVING_METHOD_CLIENT.equals(
servletContext.getInitParameter(STATE_SAVING_METHOD_PARAM_NAME));
}
+
+ static boolean shouldSavePostData(HttpServletRequest request) {
+ String secFetchSite = request.getHeader(SEC_FETCH_SITE);
+ if (secFetchSite != null && !secFetchSite.isBlank()) {
+ return "same-origin".equalsIgnoreCase(secFetchSite.trim());
+ }
+
+ return originMatchesRequest(request, request.getHeader(ORIGIN));
+ }
+
+ static boolean originMatchesRequest(HttpServletRequest request, String
originHeader) {
+ if (originHeader == null || originHeader.isBlank() ||
"null".equalsIgnoreCase(originHeader)) {
+ return false;
+ }
+
+ try {
+ URI origin = URI.create(originHeader);
+ String originScheme = origin.getScheme();
+ String originHost = origin.getHost();
+ int originPort = normalizePort(originScheme, origin.getPort());
+
+ String requestScheme = request.getScheme();
+ String requestHost = request.getServerName();
+ int requestPort = normalizePort(requestScheme,
request.getServerPort());
+
+ return Objects.equals(originScheme, requestScheme)
+ && Objects.equals(originHost, requestHost)
+ && originPort == requestPort;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+
+ private static int normalizePort(String scheme, int port) {
+ if (port >= 0) {
+ return port;
+ }
+ if (HTTPS_SCHEME.equalsIgnoreCase(scheme)) {
+ return DEFAULT_HTTPS_PORT;
+ }
+ if (HTTP_SCHEME.equalsIgnoreCase(scheme)) {
+ return DEFAULT_HTTP_PORT;
+ }
+ return -1;
+ }
}
diff --git
a/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitSupportCookies.java
b/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitSupportCookies.java
index 184c55b63..a0f7df7b8 100644
---
a/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitSupportCookies.java
+++
b/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitSupportCookies.java
@@ -13,9 +13,12 @@
*/
package org.apache.shiro.ee.filters;
+import static org.apache.shiro.SecurityUtils.getSecurityManager;
import static org.apache.shiro.ee.cdi.ShiroScopeContext.isWebContainerSessions;
import static
org.apache.shiro.ee.filters.FormResubmitSupport.getNativeSessionManager;
+import java.net.CookieManager;
import java.net.HttpCookie;
+import java.net.URI;
import java.time.Duration;
import java.util.List;
import java.util.Map;
@@ -24,12 +27,15 @@ import java.util.stream.Stream;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.http.Cookie;
+import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
+import org.apache.shiro.SecurityUtils;
import org.apache.shiro.ee.listeners.EnvironmentLoaderListener;
+import static
org.apache.shiro.web.mgt.CookieRememberMeManager.DEFAULT_REMEMBER_ME_COOKIE_NAME;
import static
org.apache.shiro.web.servlet.ShiroHttpSession.DEFAULT_SESSION_ID_NAME;
/**
@@ -96,4 +102,30 @@ public class FormResubmitSupportCookies {
static Stream<HttpCookie> cookieStreamFromHeader(@NonNull List<String>
cookies) {
return cookies.stream().map(HttpCookie::parse).map(list ->
list.get(0));
}
+
+ static void initializeCookies(URI savedRequest, ServletContext
servletContext,
+ CookieManager cookieManager,
HttpServletRequest originalRequest) {
+ var session = SecurityUtils.getSubject().getSession();
+ var sessionCookieName = getSessionCookieName(servletContext,
getSecurityManager());
+ var sessionCookie = new HttpCookie(sessionCookieName,
session.getId().toString());
+ sessionCookie.setPath(servletContext.getContextPath());
+ sessionCookie.setVersion(0);
+ cookieManager.getCookieStore().add(savedRequest, sessionCookie);
+ log.debug("Setting Cookie {}", sessionCookieName);
+ for (Cookie origCookie : originalRequest.getCookies()) {
+ if (!origCookie.getName().startsWith(sessionCookieName)
+ &&
!origCookie.getName().equals(DEFAULT_REMEMBER_ME_COOKIE_NAME)) {
+ try {
+ log.debug("Setting Cookie {}", origCookie.getName());
+ HttpCookie cookie = new HttpCookie(origCookie.getName(),
origCookie.getValue());
+ cookie.setPath(servletContext.getContextPath());
+ cookie.setVersion(0);
+ cookieManager.getCookieStore().add(savedRequest, cookie);
+ } catch (IllegalArgumentException e) {
+ log.warn("Form Resubmit: Ignoring invalid cookie [{} -
{}]",
+ origCookie.getName(), origCookie.getValue(), e);
+ }
+ }
+ }
+ }
}
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
index 85b8f70aa..72ec2f547 100644
---
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
@@ -23,9 +23,15 @@ import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
-import org.apache.shiro.SecurityUtils;
+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.decrypt;
import static
org.apache.shiro.ee.filters.FormResubmitSupport.getRememberMeManager;
import static
org.apache.shiro.web.filter.authc.NoAccessFilter.FORM_RESUBMIT_CHECK_SERVLET_PATH;
@@ -33,25 +39,23 @@ import static
org.apache.shiro.web.filter.authc.NoAccessFilter.FORM_RESUBMIT_CHE
@WebServlet(name = "ShiroFormResubmitValidator", urlPatterns =
FORM_RESUBMIT_CHECK_SERVLET_PATH)
public class FormResubmitValidator extends HttpServlet {
@Override
- protected void doGet(HttpServletRequest request, HttpServletResponse
response) {
- var session = SecurityUtils.getSubject().getSession(false);
- if (session == null) {
- response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
- return;
- }
-
+ 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 encryptedSessionId =
rememberMeManager.getCipherService()
-
.encrypt(session.getId().toString().getBytes(StandardCharsets.UTF_8),
+ String formDataKey =
decrypt(request.getReader().lines().collect(Collectors.joining()),
rememberMeManager);
+ 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(encryptedSessionId);
+ response.getWriter().write(encryptedFormDataKey);
response.setStatus(HttpServletResponse.SC_OK);
- } catch (IOException e) {
- log.warn("Form resubmit verification: failed to write
encrypted principals to response", e);
+ } 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 76d0ead3f..9413d1261 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
@@ -233,7 +233,7 @@ public class ShiroFilter extends
org.apache.shiro.web.servlet.ShiroFilter {
}
@Override
- @SneakyThrows
+ @SneakyThrows(InterruptedException.class)
protected void executeChain(ServletRequest request, ServletResponse
response,
FilterChain origChain) throws IOException, ServletException {
if (isShiroEEDisabled(getServletContext())) {
@@ -245,7 +245,7 @@ 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,
+ Optional.ofNullable(resubmitSavedForm(postData, null,
Servlets.getRequestURLWithQueryString(httpRequest),
WebUtils.toHttp(request), WebUtils.toHttp(response),
request.getServletContext(), rememberedAjaxResubmit,
false))
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 db1fecdd5..4b2614234 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
@@ -52,7 +52,7 @@ public class NoAccessFilter extends AuthenticatingFilter {
protected boolean isAccessAllowed(ServletRequest request, ServletResponse
response, Object mappedValue) {
if (request instanceof HttpServletRequest) {
HttpServletRequest httpRequest = WebUtils.toHttp(request);
- return httpRequest.getMethod().equals("GET")
+ return httpRequest.getMethod().equals("POST")
&&
httpRequest.getServletPath().equals(FORM_RESUBMIT_CHECK_SERVLET_PATH);
}
return false;
diff --git
a/web/src/main/java/org/apache/shiro/web/servlet/ShiroHttpServletResponse.java
b/web/src/main/java/org/apache/shiro/web/servlet/ShiroHttpServletResponse.java
index e645f7b36..c2c93ae3c 100644
---
a/web/src/main/java/org/apache/shiro/web/servlet/ShiroHttpServletResponse.java
+++
b/web/src/main/java/org/apache/shiro/web/servlet/ShiroHttpServletResponse.java
@@ -27,6 +27,10 @@ import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
+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;
+import static org.apache.shiro.web.filter.authz.SslFilter.HTTPS_SCHEME;
/**
* HttpServletResponse implementation to support URL Encoding of Shiro Session
IDs.
@@ -183,18 +187,18 @@ public class ShiroHttpServletResponse extends
HttpServletResponseWrapper {
}
int serverPort = hreq.getServerPort();
if (serverPort == -1) {
- if ("https".equals(hreq.getScheme())) {
- serverPort = 443;
+ if (HTTPS_SCHEME.equals(hreq.getScheme())) {
+ serverPort = DEFAULT_HTTPS_PORT;
} else {
- serverPort = 80;
+ serverPort = DEFAULT_HTTP_PORT;
}
}
int urlPort = url.getPort();
if (urlPort == -1) {
- if ("https".equals(url.getProtocol())) {
- urlPort = 443;
+ if (HTTPS_SCHEME.equals(url.getProtocol())) {
+ urlPort = DEFAULT_HTTPS_PORT;
} else {
- urlPort = 80;
+ urlPort = DEFAULT_HTTP_PORT;
}
}
if (serverPort != urlPort) {
@@ -208,9 +212,7 @@ public class ShiroHttpServletResponse extends
HttpServletResponseWrapper {
return (false);
}
String tok = ";" + DEFAULT_SESSION_ID_PARAMETER_NAME + "=" +
session.getId();
- if (file.indexOf(tok, contextPath.length()) >= 0) {
- return (false);
- }
+ return file.indexOf(tok, contextPath.length()) < 0;
}
// This URL belongs to our web application, so it is encodeable
@@ -248,8 +250,8 @@ public class ShiroHttpServletResponse extends
HttpServletResponseWrapper {
try {
buf.append(scheme).append("://").append(name);
- if ((scheme.equals("http") && port != 80)
- || (scheme.equals("https") && port != 443)) {
+ if ((scheme.equals(HTTP_SCHEME) && port != DEFAULT_HTTP_PORT)
+ || (scheme.equals(HTTPS_SCHEME) && port !=
DEFAULT_HTTPS_PORT)) {
buf.append(':').append(port);
}
if (!leadingSlash) {
@@ -262,9 +264,7 @@ public class ShiroHttpServletResponse extends
HttpServletResponseWrapper {
}
buf.append(location);
} catch (IOException e) {
- IllegalArgumentException iae = new
IllegalArgumentException(location);
- iae.initCause(e);
- throw iae;
+ throw new IllegalArgumentException(location, e);
}
return buf.toString();
@@ -333,7 +333,7 @@ public class ShiroHttpServletResponse extends
HttpServletResponseWrapper {
}
StringBuilder sb = new StringBuilder(path);
// session id param can't be first.
- if (sb.length() > 0) {
+ if (!sb.isEmpty()) {
sb.append(";");
sb.append(DEFAULT_SESSION_ID_PARAMETER_NAME);
sb.append("=");