Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
72c9564
enh: rework form resubmit via self-dispatch rather than a network-call
lprimak Sep 29, 2026
c1f8949
chore: moved meecrowave slf4j into a variable
lprimak Sep 29, 2026
eaffb39
chore: removed extra blank line
lprimak Sep 29, 2026
8497768
Merge branch 'main' into remove-resubmit-netcall
lprimak Sep 29, 2026
ce5ebc5
revert integration-tests/jakarta-ee/src/main/java/org/apache/shiro/te…
lprimak Sep 30, 2026
3c02bc1
removed AI slop test
lprimak Sep 30, 2026
fc86f59
removed extra added newline
lprimak Sep 30, 2026
4558517
simplificatino round 1
lprimak Sep 30, 2026
a583015
removed some methods
lprimak Sep 30, 2026
e444097
simplify
lprimak Sep 30, 2026
6705073
more simplification
lprimak Oct 1, 2026
0f2dd4a
added selenium BOM
lprimak Oct 1, 2026
78782a3
RAT check: run only at root of the tree
lprimak Oct 1, 2026
0fad87f
Merge branch 'main' into remove-resubmit-netcall
lprimak Oct 3, 2026
aa3906f
further code simplification
lprimak Oct 3, 2026
ed0ec6e
Merge branch 'main' into remove-resubmit-netcall
lprimak Oct 7, 2026
9e6b326
cleanup
lprimak Oct 7, 2026
cd0bdef
FormResubmitRequest.java cleanup
lprimak Oct 8, 2026
7868627
Cleanup of ShiroFilter.java
lprimak Oct 8, 2026
eee4a59
more cleanup
lprimak Oct 8, 2026
54d4555
more cleanup
lprimak Oct 8, 2026
625f317
spelling
lprimak Oct 8, 2026
d9bb20f
more cleanup
lprimak Oct 8, 2026
88a05e7
removal of more dead code and more code reuse
lprimak Oct 8, 2026
e8c46ae
more simplification
lprimak Oct 8, 2026
98238bb
cleanup
lprimak Oct 8, 2026
cf2d8e5
more simplification
lprimak Oct 8, 2026
b276725
more cleanup
lprimak Oct 8, 2026
af12a12
clean up
lprimak Oct 8, 2026
999264c
chore: consolidated Location / Set-Cookie constants
lprimak Oct 8, 2026
e04a262
reverted set-cookie - only used once
lprimak Oct 8, 2026
89956b2
enh: keep in-place Faces Ajax form replay as an Ajax request
lprimak Oct 8, 2026
55d546b
don't log full form data
lprimak Oct 8, 2026
e6f23ac
Using __Host prefix for resubmit cookie
lprimak Oct 9, 2026
4556711
form data discard API
lprimak Oct 9, 2026
07e3cc0
README
lprimak Oct 9, 2026
41a2601
fixed test
lprimak Oct 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,13 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se

LogRecord record = LogCapture.get().poll();
while (record != null) {
if (record.getThrown() != null) {
out.printf("%s: %s", record.getLevel(), record.getThrown());
Throwable thrown = record.getThrown();
// Ignore the Payara logging bug on JDK 27, but keep reporting other exceptions.
boolean payaraLoggingBug = thrown instanceof NullPointerException
&& ("Cannot invoke \"java.util.ResourceBundle.getString(String)\" because the return value of "
+ "\"java.util.logging.Logger.getResourceBundle()\" is null").equals(thrown.getMessage());
if (thrown != null && !payaraLoggingBug) {
out.printf("%s: %s", record.getLevel(), thrown);
out.print(System.lineSeparator());
}
record = LogCapture.get().poll();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ void deleteAllCookies() {
webDriver.manage().deleteAllCookies();
}


@Test
@OperateOnDeployment(DEPLOYMENT_DEV_MODE)
void protectedPageWithLogin() {
Expand Down Expand Up @@ -222,13 +223,17 @@ void incorrectLoginOnce() {
@Test
@OperateOnDeployment(DEPLOYMENT_DEV_MODE)
void nonAjaxSessionExpired() {
nonAjaxSessionExpired("Jack", "Frost");
}

private void nonAjaxSessionExpired(String first, String last) {
webDriver.get(baseURL + "shiro/form");
login();
invalidateSession.click();
waitGui(webDriver).until(ExpectedConditions.alertIsPresent());
webDriver.switchTo().alert().accept();
firstName.sendKeys("Jack");
lastName.sendKeys("Frost");
firstName.sendKeys(first);
lastName.sendKeys(last);
guardHttp(submitFirst).click();
assertThat(sessionExpiredMessage.getText()).isEqualTo("Your Session Has Expired");
}
Expand All @@ -241,6 +246,14 @@ void nonAjaxResubmit() {
assertThat(messages.getText()).isEqualTo("Form Submitted - firstName: Jack, lastName: Frost");
}

@Test
@OperateOnDeployment(DEPLOYMENT_DEV_MODE)
void nonAjaxResubmitPreservesEscapedInput() {
nonAjaxSessionExpired("Jörg & Sons + =", "Frost 雪");
login();
assertThat(messages.getText()).isEqualTo("Form Submitted - firstName: Jörg & Sons + =, lastName: Frost 雪");
}

@Test
@OperateOnDeployment(DEPLOYMENT_DEV_MODE)
void nonAjaxResubmitAfterFailedLogin() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Licensed 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.testing.jakarta.ee.servlets;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

import jakarta.servlet.http.HttpServletResponse;
import org.apache.shiro.testing.logcapture.LogCapture;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;

import static org.assertj.core.api.Assertions.assertThat;
import static org.easymock.EasyMock.createNiceMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;

@Execution(ExecutionMode.SAME_THREAD)
class ExceptionServletTest {
private static final int LOG_CAPACITY = 10;
private static final String PAYARA_MESSAGE = "Cannot invoke \"java.util.ResourceBundle.getString(String)\" "
+ "because the return value of \"java.util.logging.Logger.getResourceBundle()\" is null";

@BeforeEach
void setupLogging() {
LogCapture.get().setupLogging(LOG_CAPACITY);
}

@AfterEach
void resetLogging() {
LogCapture.get().resetLogging();
}

@Test
void ignoresPayaraLoggingBug() throws Exception {
log(new NullPointerException(PAYARA_MESSAGE));
log(new NullPointerException(PAYARA_MESSAGE));

assertThat(getResponse()).isEmpty();
assertThat(getResponse()).isEmpty();
}

@Test
void reportsOtherExceptionsAfterPayaraLoggingBug() throws Exception {
log(new NullPointerException(PAYARA_MESSAGE));
log(null);
log(new NullPointerException("another bug"));
log(new NullPointerException());
log(new IllegalStateException(PAYARA_MESSAGE));

String newline = System.lineSeparator();
assertThat(getResponse()).isEqualTo("WARNING: java.lang.NullPointerException: another bug" + newline
+ "WARNING: java.lang.NullPointerException" + newline
+ "WARNING: java.lang.IllegalStateException: " + PAYARA_MESSAGE + newline);
assertThat(getResponse()).isEmpty();
}

private void log(Throwable thrown) {
LogRecord record = new LogRecord(Level.WARNING, "test exception");
record.setThrown(thrown);
Logger.getLogger("").log(record);
}

private String getResponse() throws Exception {
StringWriter output = new StringWriter();
HttpServletResponse response = createNiceMock(HttpServletResponse.class);
expect(response.getWriter()).andReturn(new PrintWriter(output));
replay(response);

new ExceptionServlet().doGet(null, response);
return output.toString();
}
}
2 changes: 1 addition & 1 deletion integration-tests/meecrowave-support/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>2.0.20</version>
<version>${slf4j.version}</version>
<scope>runtime</scope>
</dependency>

Expand Down
43 changes: 43 additions & 0 deletions support/jakarta-ee/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<!--
Licensed 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.
-->

# Jakarta EE form resubmission

Saved forms are replayed within the current web application using
`RequestDispatcher.forward`, without an outbound HTTP connection. The replay
uses the current Shiro subject, session, and browser response. Its request body
and form parameters replace those of the login request.

For server-side Faces state saving, a buffered GET obtains a new view state
before the POST. Remembered Ajax submissions retain the two-POST flow, buffering
intermediate responses. A calling Faces context is restored after each dispatch.
The successful POST's cookies are preserved unchanged; the expired-view probe
must not replace its flash cookie and lose submitted-form messages.

## Application filter configuration

Shiro's Jakarta EE filter is mapped to `DispatcherType.FORWARD`, so the forwarded
target's security chain runs again. Application filters needed during replay
must also be mapped to `FORWARD`, not only `REQUEST`. Leave Shiro's
`filterOncePerRequest` disabled when using form resubmission.

Replay remains in the same servlet request lifecycle. Application filters and
request-scoped components should not assume that a replay starts a new external
request. Saved targets must be within the current context; servlet-private
`WEB-INF` and `META-INF` resources cannot be replay targets.

The old `org.apache.shiro.form-resubmit-host`,
`org.apache.shiro.form-resubmit-port`, and form-resubmit blacklist settings are
no longer used. Saved-form cookies still use the existing secure-cookie setting;
there is no separate replay cookie jar or cookie-header rewriting.
Loading
Loading