This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch 11.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/11.0.x by this push:
new 10d048e160 Refresh constraints after FORM auth if method changes
10d048e160 is described below
commit 10d048e16034ddf12055e0cede0da05b15c823b8
Author: Mark Thomas <[email protected]>
AuthorDate: Thu Jul 30 14:41:30 2026 +0100
Refresh constraints after FORM auth if method changes
---
.../catalina/authenticator/AuthenticatorBase.java | 149 ++++++++--
.../catalina/authenticator/FormAuthenticator.java | 60 ++--
.../authenticator/TestFormAuthenticatorD.java | 315 +++++++++++++++++++++
webapps/docs/changelog.xml | 9 +
4 files changed, 474 insertions(+), 59 deletions(-)
diff --git a/java/org/apache/catalina/authenticator/AuthenticatorBase.java
b/java/org/apache/catalina/authenticator/AuthenticatorBase.java
index f42febc29a..c095bc4068 100644
--- a/java/org/apache/catalina/authenticator/AuthenticatorBase.java
+++ b/java/org/apache/catalina/authenticator/AuthenticatorBase.java
@@ -536,34 +536,14 @@ public abstract class AuthenticatorBase extends ValveBase
implements Authenticat
return;
}
- // Make sure that constrained resources are not cached by web proxies
- // or browsers as caching can provide a security hole
- if (constraints != null && disableProxyCaching &&
!Method.POST.equals(request.getMethod())) {
- if (securePagesWithPragma) {
- // Note: These can cause problems with downloading files with
IE
- response.setHeader("Pragma", "No-cache");
- response.setHeader("Cache-Control", "no-cache");
- response.setHeader("Expires", DATE_ONE);
- } else {
- response.setHeader("Cache-Control", "private");
- }
- }
+ /*
+ * Make sure that constrained resources are not cached by web proxies
or browsers as caching can provide a
+ * security hole.
+ */
+ disableCaching(constraints, request, response);
- if (constraints != null) {
- // Enforce any user data constraint for this security constraint
- if (log.isTraceEnabled()) {
- log.trace("Calling hasUserDataPermission()");
- }
- if (!realm.hasUserDataPermission(request, response, constraints)) {
- if (log.isDebugEnabled()) {
-
log.debug(sm.getString("authenticator.userDataPermissionFail"));
- }
- /*
- * ASSERT: Authenticator already set the appropriate HTTP
status code, so we do not have to do anything
- * special
- */
- return;
- }
+ if (!checkUserDataConstraints(realm, constraints, request, response)) {
+ return;
}
// Since authenticate modifies the response on failure,
@@ -608,15 +588,29 @@ public abstract class AuthenticatorBase extends ValveBase
implements Authenticat
log.trace("Calling authenticate()");
}
- if (jaspicProvider != null) {
+ boolean authenticated;
+ if (jaspicProvider == null) {
+ AuthenticationResult authenticationResult =
doAuthenticateExtended(request, response);
+ if (authenticationResult ==
AuthenticationResult.PASSED_CONSTRAINTS_NEED_REFRESH) {
+ // Recalculate constraints since the request has changed
+ constraints = realm.findSecurityConstraints(request,
this.context);
+ // Re-check if caching needs to be disabled since the
request (and maybe constraints) have changed
+ disableCaching(constraints, request, response);
+ // Re-check user data constraints as constraints may have
changed
+ if (!checkUserDataConstraints(realm, constraints, request,
response)) {
+ return;
+ }
+ }
+ authenticated = authenticationResult.getAuthenticated();
+ } else {
jaspicState = getJaspicState(jaspicProvider, request,
response, hasAuthConstraint);
if (jaspicState == null) {
return;
}
+ authenticated = authenticateJaspic(request, response,
jaspicState, false);
}
- if (jaspicProvider == null && !doAuthenticate(request, response) ||
- jaspicProvider != null && !authenticateJaspic(request,
response, jaspicState, false)) {
+ if (!authenticated) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("authenticator.authenticationFail"));
}
@@ -658,6 +652,43 @@ public abstract class AuthenticatorBase extends ValveBase
implements Authenticat
}
+ private void disableCaching(SecurityConstraint[] constraints, Request
request, Response response) {
+ // Only disable caching where necessary
+ if (constraints != null && disableProxyCaching &&
!Method.POST.equals(request.getMethod())) {
+ if (securePagesWithPragma) {
+ // Note: These can cause problems with downloading files with
IE
+ response.setHeader("Pragma", "No-cache");
+ response.setHeader("Cache-Control", "no-cache");
+ response.setHeader("Expires", DATE_ONE);
+ } else {
+ response.setHeader("Cache-Control", "private");
+ }
+ }
+ }
+
+
+ private boolean checkUserDataConstraints(Realm realm, SecurityConstraint[]
constraints, Request request,
+ Response response) throws IOException {
+ if (constraints != null) {
+ // Enforce any user data constraint for this security constraint
+ if (log.isTraceEnabled()) {
+ log.trace("Calling hasUserDataPermission()");
+ }
+ if (!realm.hasUserDataPermission(request, response, constraints)) {
+ if (log.isDebugEnabled()) {
+
log.debug(sm.getString("authenticator.userDataPermissionFail"));
+ }
+ /*
+ * ASSERT: Authenticator already set the appropriate HTTP
status code, so we do not have to do anything
+ * special
+ */
+ return false;
+ }
+ }
+ return true;
+ }
+
+
/**
* Determines whether a CORS preflight request should bypass
authentication.
*
@@ -723,7 +754,8 @@ public abstract class AuthenticatorBase extends ValveBase
implements Authenticat
AuthConfigProvider jaspicProvider = getJaspicProvider();
if (jaspicProvider == null) {
- return doAuthenticate(request, httpResponse);
+ // Just authenticating so no requirement to refresh constraints
+ return doAuthenticateExtended(request,
httpResponse).getAuthenticated();
} else {
Response response = request.getResponse();
JaspicState jaspicState = getJaspicState(jaspicProvider, request,
response, true);
@@ -827,6 +859,28 @@ public abstract class AuthenticatorBase extends ValveBase
implements Authenticat
protected abstract boolean doAuthenticate(Request request,
HttpServletResponse response) throws IOException;
+ /**
+ * Extended mechanism for sub-class authentication that adds the option to
trigger a refresh of the security
+ * constraints as required with FORM authentication if the method changes.
Most sub-classes will just implement
+ * {@link #doAuthenticate(Request, HttpServletResponse)}.
+ *
+ * @param request The request that triggered the authentication
+ * @param response The response associated with the request
+ *
+ * @return the result of the authentication
+ *
+ * @throws IOException If an I/O problem occurred during the
authentication process
+ */
+ protected AuthenticationResult doAuthenticateExtended(Request request,
HttpServletResponse response)
+ throws IOException {
+ if (doAuthenticate(request, response)) {
+ return AuthenticationResult.PASSED;
+ } else {
+ return AuthenticationResult.FAILED;
+ }
+ }
+
+
/**
* Does this authenticator require that {@link #authenticate(Request,
HttpServletResponse)} is called to continue an
* authentication process that started in a previous request?
@@ -1456,4 +1510,37 @@ public abstract class AuthenticatorBase extends
ValveBase implements Authenticat
*/
FULL
}
+
+
+ /**
+ * Used to pass authentication results that are more complex than a simple
pass/fail.
+ */
+ protected enum AuthenticationResult {
+
+ /**
+ * The authentication failed.
+ */
+ FAILED(false),
+
+ /**
+ * The authentication was successful but before proceeding the
constraints need to be refreshed because one or
+ * more relevant properties of the request (method, URI) have changed.
+ */
+ PASSED_CONSTRAINTS_NEED_REFRESH(true),
+
+ /**
+ * The authentication was successful.
+ */
+ PASSED(true);
+
+ private final boolean authenticated;
+
+ AuthenticationResult(boolean authenticated) {
+ this.authenticated = authenticated;
+ }
+
+ public boolean getAuthenticated() {
+ return authenticated;
+ }
+ }
}
diff --git a/java/org/apache/catalina/authenticator/FormAuthenticator.java
b/java/org/apache/catalina/authenticator/FormAuthenticator.java
index 5480c1186c..2e3ee1b9e9 100644
--- a/java/org/apache/catalina/authenticator/FormAuthenticator.java
+++ b/java/org/apache/catalina/authenticator/FormAuthenticator.java
@@ -148,18 +148,10 @@ public class FormAuthenticator extends AuthenticatorBase {
// ------------------------------------------------------ Protected Methods
- /**
- * Authenticate the user making this request, based on the specified login
configuration. Return <code>true</code>
- * if any specified constraint has been satisfied, or <code>false</code>
if we have created a response challenge
- * already.
- *
- * @param request Request we are processing
- * @param response Response we are creating
- *
- * @exception IOException if an input/output error occurs
- */
+
@Override
- protected boolean doAuthenticate(Request request, HttpServletResponse
response) throws IOException {
+ protected AuthenticationResult doAuthenticateExtended(Request request,
HttpServletResponse response)
+ throws IOException {
// References to objects we will need later
Session session = null;
@@ -181,7 +173,7 @@ public class FormAuthenticator extends AuthenticatorBase {
if (principal != null) {
register(request, response, principal,
HttpServletRequest.FORM_AUTH, username, password);
if (!matchRequest(request)) {
- return true;
+ return AuthenticationResult.PASSED;
}
}
if (log.isDebugEnabled()) {
@@ -197,24 +189,24 @@ public class FormAuthenticator extends AuthenticatorBase {
if (log.isTraceEnabled()) {
log.trace("Restore request from session '" +
session.getIdInternal() + "'");
}
- if (restoreRequest(request, session)) {
+ AuthenticationResult result = restoreRequest(request, session);
+ if (result.getAuthenticated()) {
if (log.isTraceEnabled()) {
log.trace("Proceed to restored request");
}
- return true;
} else {
if (log.isDebugEnabled()) {
log.debug(sm.getString("formAuthenticator.restoreFailed"));
}
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
- return false;
}
+ return result;
}
// This check has to be after the previous check for a matching request
// because that matching request may also include a cached Principal.
if (checkForCachedAuthentication(request, response, true)) {
- return true;
+ return AuthenticationResult.PASSED;
}
// Acquire references to objects we will need to evaluate
@@ -239,7 +231,7 @@ public class FormAuthenticator extends AuthenticatorBase {
location.append(request.getQueryString());
}
response.sendRedirect(response.encodeRedirectURL(location.toString()));
- return false;
+ return AuthenticationResult.FAILED;
}
session = request.getSessionInternal(true);
@@ -251,10 +243,10 @@ public class FormAuthenticator extends AuthenticatorBase {
} catch (IOException ioe) {
log.debug(sm.getString("authenticator.requestBodyTooBig"),
ioe);
response.sendError(HttpServletResponse.SC_FORBIDDEN,
sm.getString("authenticator.requestBodyTooBig"));
- return false;
+ return AuthenticationResult.FAILED;
}
forwardToLoginPage(request, response, config);
- return false;
+ return AuthenticationResult.FAILED;
}
// Yes -- Acknowledge the request, validate the specified credentials
@@ -272,7 +264,7 @@ public class FormAuthenticator extends AuthenticatorBase {
principal = realm.authenticate(username, password);
if (principal == null) {
forwardToErrorPage(request, response, config);
- return false;
+ return AuthenticationResult.FAILED;
}
if (log.isTraceEnabled()) {
@@ -311,7 +303,7 @@ public class FormAuthenticator extends AuthenticatorBase {
request.getSessionInternal(true).setNote(Constants.FORM_REQUEST_NOTE, saved);
response.sendRedirect(response.encodeRedirectURL(uri));
}
- return false;
+ return AuthenticationResult.FAILED;
}
register(request, response, principal, HttpServletRequest.FORM_AUTH,
username, password);
@@ -344,7 +336,14 @@ public class FormAuthenticator extends AuthenticatorBase {
response.sendRedirect(location, HttpServletResponse.SC_FOUND);
}
}
- return false;
+ return AuthenticationResult.FAILED;
+ }
+
+
+ @Override
+ protected boolean doAuthenticate(Request request, HttpServletResponse
response) throws IOException {
+ // This method should never be called
+ throw new UnsupportedOperationException();
}
@@ -514,7 +513,7 @@ public class FormAuthenticator extends AuthenticatorBase {
*
* @param request The request to be verified
* @param strict <code>true</code> to check for a valid Principal and
valid Session ID, <code>false</code> to only
- * check for a valid saved request and matching URI
+ * check for a valid saved request and matching URI
*
* @return <code>true</code> if the requests matched the saved one
*/
@@ -557,23 +556,23 @@ public class FormAuthenticator extends AuthenticatorBase {
/**
* Restore the original request from information stored in our session. If
the original request is no longer present
- * (because the session timed out), return <code>false</code>; otherwise,
return <code>true</code>.
+ * (because the session timed out), it will be treated as a failure to
restore the request.
*
* @param request The request to be restored
* @param session The session containing the saved information
*
- * @return <code>true</code> if the request was successfully restored
+ * @return the status of the FORM authentication process based on whether
the original request could be restored
*
* @throws IOException if an IO error occurred during the process
*/
- protected boolean restoreRequest(Request request, Session session) throws
IOException {
+ protected AuthenticationResult restoreRequest(Request request, Session
session) throws IOException {
// Retrieve and remove the SavedRequest object from our session
SavedRequest saved = (SavedRequest)
session.getNote(Constants.FORM_REQUEST_NOTE);
session.removeNote(Constants.FORM_REQUEST_NOTE);
session.removeNote(Constants.SESSION_ID_NOTE);
if (saved == null) {
- return false;
+ return AuthenticationResult.FAILED;
}
// Swallow any request body since we will be replacing it
@@ -638,6 +637,7 @@ public class FormAuthenticator extends AuthenticatorBase {
request.getCoyoteRequest().setContentType(contentType);
}
+ boolean methodChanged =
!request.getCoyoteRequest().getMethod().equals(method);
request.getCoyoteRequest().setMethod(method);
// The method, URI, queryString and protocol are normally stored as
// bytes in the HttpInputBuffer and converted lazily to String. At this
@@ -656,7 +656,11 @@ public class FormAuthenticator extends AuthenticatorBase {
session.setMaxInactiveInterval(saved.getOriginalMaxInactiveIntervalOptional().intValue());
}
- return true;
+ if (methodChanged) {
+ return AuthenticationResult.PASSED_CONSTRAINTS_NEED_REFRESH;
+ } else {
+ return AuthenticationResult.PASSED;
+ }
}
diff --git a/test/org/apache/catalina/authenticator/TestFormAuthenticatorD.java
b/test/org/apache/catalina/authenticator/TestFormAuthenticatorD.java
new file mode 100644
index 0000000000..cdc9d6977c
--- /dev/null
+++ b/test/org/apache/catalina/authenticator/TestFormAuthenticatorD.java
@@ -0,0 +1,315 @@
+/*
+ * 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.catalina.authenticator;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.startup.SimpleHttpClient;
+import org.apache.catalina.startup.TesterMapRealm;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.tomcat.util.descriptor.web.LoginConfig;
+import org.apache.tomcat.util.descriptor.web.SecurityCollection;
+import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
+import org.apache.tomcat.util.http.Method;
+
+public class TestFormAuthenticatorD extends TomcatBaseTest {
+
+ private static final String CRLF = SimpleHttpClient.CRLF;
+
+ private static final String STANDARD_USER = "userA";
+ private static final String ADMIN_USER = "userB";
+
+
+ @Test
+ public void testWithStandardUser() throws Exception {
+ SimpleHttpClient client = setupTest();
+
+ // Standard user requesting GET /standard
+ // Initial request - receive login form
+ requestStandard(client, null, true);
+ // Submit login form - receive redirect
+ submitFormAuth(client, STANDARD_USER);
+ // Request original page
+ requestStandard(client, STANDARD_USER, false);
+
+ // As authenticated standard user, request GET /admin
+ requestAdmin(client, STANDARD_USER, Method.GET, Method.GET, false);
+
+ // As authenticated standard user, request POST /admin
+ requestAdmin(client, STANDARD_USER, Method.POST, Method.POST, false);
+
+ // Clear authentication
+ client.setSessionId(null);
+
+ // As unauthenticated user, request GET /admin
+ requestAdmin(client, null, Method.GET, Method.GET, false);
+
+ // As unauthenticated user, request POST /admin
+ requestAdmin(client, null, Method.POST, Method.POST, true);
+ // Submit login form - receive redirect
+ submitFormAuth(client, STANDARD_USER);
+ // As standard user, request POST /admin
+ requestAdmin(client, STANDARD_USER, Method.GET, Method.POST, false);
+ }
+
+
+ @Test
+ public void testWithAdminUser() throws Exception {
+ SimpleHttpClient client = setupTest();
+
+ // Standard user requesting GET /standard
+ // Initial request - receive login form
+ requestStandard(client, null, true);
+ // Submit login form - receive redirect
+ submitFormAuth(client, ADMIN_USER);
+ // Request original page
+ requestStandard(client, ADMIN_USER, false);
+
+ // As authenticated standard user, request GET /admin
+ requestAdmin(client, ADMIN_USER, Method.GET, Method.GET, false);
+
+ // As authenticated standard user, request POST /admin
+ requestAdmin(client, ADMIN_USER, Method.POST, Method.POST, false);
+
+ // Clear authentication
+ client.setSessionId(null);
+
+ // As unauthenticated user, request GET /admin
+ requestAdmin(client, null, Method.GET, Method.GET, false);
+
+ // As unauthenticated user, request POST /admin
+ requestAdmin(client, null, Method.POST, Method.POST, true);
+ // Submit login form - receive redirect
+ submitFormAuth(client, ADMIN_USER);
+ // As standard user, request POST /admin
+ requestAdmin(client, ADMIN_USER, Method.GET, Method.POST, false);
+ }
+
+
+ private SimpleHttpClient setupTest() throws Exception {
+ Tomcat tomcat = getTomcatInstance();
+
+ // No file system docBase required
+ StandardContext ctx = (StandardContext) getProgrammaticRootContext();
+
+ // Add Servlets
+ Tomcat.addServlet(ctx, "login", new LoginServlet());
+ Tomcat.addServlet(ctx, "error", new ErrorServlet());
+ Tomcat.addServlet(ctx, "target", new TargetServlet());
+ // Map Servlets (target gets mapped twice)
+ ctx.addServletMapping("/login", "login");
+ ctx.addServletMapping("/error", "error");
+ ctx.addServletMapping("/standard", "target");
+ ctx.addServletMapping("/admin", "target");
+
+ // Configure the Realm
+ TesterMapRealm realm = new TesterMapRealm();
+ realm.addUser(STANDARD_USER, STANDARD_USER);
+ realm.addUserRole(STANDARD_USER, "standard");
+ realm.addUser(ADMIN_USER, ADMIN_USER);
+ realm.addUserRole(ADMIN_USER, "standard");
+ realm.addUserRole(ADMIN_USER, "admin");
+ ctx.setRealm(realm);
+
+ // Configure the security constraints
+ // /standard is protected for all methods
+ SecurityConstraint constraintStandard = new SecurityConstraint();
+ SecurityCollection collectionStandard = new SecurityCollection();
+ collectionStandard.setName("Protect standard");
+ collectionStandard.addPattern("/standard");
+ constraintStandard.addCollection(collectionStandard);
+ constraintStandard.addAuthRole("standard");
+ ctx.addConstraint(constraintStandard);
+ // /admin is only protected for POST
+ SecurityConstraint constraintAdmin = new SecurityConstraint();
+ SecurityCollection collectionAdmin = new SecurityCollection();
+ collectionAdmin.addMethod("POST");
+ collectionAdmin.setName("Protect admin");
+ collectionAdmin.addPattern("/admin");
+ constraintAdmin.addCollection(collectionAdmin);
+ constraintAdmin.addAuthRole("admin");
+ ctx.addConstraint(constraintAdmin);
+
+ // Configure authentication
+ LoginConfig lc = new LoginConfig();
+ lc.setAuthMethod("FORM");
+ lc.setLoginPage("/login");
+ lc.setErrorPage("/error");
+ ctx.setLoginConfig(lc);
+ ctx.getPipeline().addValve(new FormAuthenticator());
+
+ tomcat.start();
+
+ TestHttpClient client = new TestHttpClient();
+ client.setPort(getPort());
+ client.setUseContentLength(true);
+ client.setUseCookies(true);
+ client.setRequestPause(0);
+ client.connect();
+
+ return client;
+ }
+
+ private void submitFormAuth(SimpleHttpClient client, String user) throws
Exception {
+ client.setRequest(new String[] {
+ "POST /j_security_check HTTP/1.1" + CRLF,
+ "Host: localhost:" + getPort() + CRLF,
+ "Cookie: JSESSIONID=" + client.getSessionId() + CRLF,
+ "Content-Type: application/x-www-form-urlencoded" + CRLF,
+ "Content-Length: " + (23 + user.length() * 2) + CRLF,
+ CRLF,
+ "j_username=" + user + "&j_password=" + user });
+ client.processRequest();
+ Assert.assertEquals(303, client.getStatusCode());
+ client.resetResponse();
+ }
+
+
+ private void requestStandard(SimpleHttpClient client, String user, boolean
authRedirectExpected) throws Exception {
+ String sessionID = client.getSessionId();
+
+ client.setRequest(new String[] {
+ "GET /standard HTTP/1.1" + CRLF,
+ "Host: localhost:" + getPort() + CRLF,
+ "Cookie: " + (sessionID == null ? "a=b" : "JSESSIONID=" +
sessionID) + CRLF,
+ CRLF });
+ client.processRequest();
+ Assert.assertEquals(200, client.getStatusCode());
+ if (authRedirectExpected) {
+
Assert.assertTrue(client.getResponseBody().contains("j_security_check"));
+ } else {
+ Assert.assertEquals("GET" + System.lineSeparator() + user +
System.lineSeparator() + ADMIN_USER.equals(user) +
+ System.lineSeparator() + "null" + System.lineSeparator(),
client.getResponseBody());
+ }
+ client.resetResponse();
+ }
+
+
+ private void requestAdmin(SimpleHttpClient client, String user, String
requestMethod, String resultMethod,
+ boolean authRedirectExpected) throws Exception {
+ String sessionID = client.getSessionId();
+
+ client.setRequest(
+ new String[] {
+ requestMethod + " /admin HTTP/1.1" + CRLF,
+ "Host: localhost:" + getPort() + CRLF,
+ "Cookie: " + (sessionID == null ? "a=b" :
"JSESSIONID=" + sessionID) + CRLF,
+ CRLF });
+ client.processRequest();
+ String body = client.getResponseBody();
+ if (authRedirectExpected) {
+ Assert.assertEquals(body, 200, client.getStatusCode());
+ Assert.assertTrue(body, body.contains("j_security_check"));
+ } else {
+ if (Method.POST.equals(resultMethod)) {
+ if (ADMIN_USER.equals(user)) {
+ Assert.assertEquals(body, 200, client.getStatusCode());
+ Assert.assertEquals(body, resultMethod +
System.lineSeparator() + user + System.lineSeparator() +
+ "true" + System.lineSeparator() + "null" +
System.lineSeparator(),
+ client.getResponseBody());
+ } else {
+ Assert.assertEquals(body, 403, client.getStatusCode());
+ }
+ } else {
+ Assert.assertEquals(body, 200, client.getStatusCode());
+ Assert.assertEquals(body, resultMethod +
System.lineSeparator() + user + System.lineSeparator() +
+ ADMIN_USER.equals(user) + System.lineSeparator() +
"null" + System.lineSeparator(),
+ client.getResponseBody());
+ }
+ }
+ client.resetResponse();
+ }
+
+
+ private static class TestHttpClient extends SimpleHttpClient {
+
+ @Override
+ public boolean isResponseBodyOK() {
+ return true;
+ }
+ }
+
+
+ private static class LoginServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
+ resp.setContentType("text/html");
+ resp.setCharacterEncoding(StandardCharsets.UTF_8);
+ PrintWriter pw = resp.getWriter();
+ pw.println("<html>");
+ pw.println("<body>");
+ pw.println("<form method=\"post\" action=\"j_security_check\">");
+ pw.println("<input name=\"j_username\">");
+ pw.println("<input name=\"j_password\" type=\"password\">");
+ pw.println("<button type=\"submit\">Login</button>");
+ pw.println("</form>");
+ pw.println("</body>");
+ pw.println("</html>");
+ }
+ }
+
+
+ private static class ErrorServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
+ resp.setContentType("text/plain");
+ resp.setCharacterEncoding(StandardCharsets.UTF_8);
+ PrintWriter pw = resp.getWriter();
+ pw.println("Login failed");
+ }
+ }
+
+
+ private static class TargetServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
+ resp.setContentType("text/plain");
+ resp.setCharacterEncoding(StandardCharsets.UTF_8);
+ PrintWriter pw = resp.getWriter();
+ pw.println(req.getMethod());
+ pw.println(req.getRemoteUser());
+ pw.println(req.isUserInRole("admin"));
+ pw.println(req.getParameter("action"));
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest req, HttpServletResponse
resp) throws ServletException, IOException {
+ doGet(req, resp);
+ }
+ }
+}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 43d6c6d834..9d7bbb7d85 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -193,6 +193,15 @@
Ensure the security constraint with the longest matching path is
selected when more than one constraint matches the request path.
(markt)
</fix>
+ <fix>
+ If the request saved by FORM authentication uses a method other than
+ GET, ensure that the security constraints are re-assessed after the
+ saved request is restored and before it is processed. Custom
+ <code>Authenticator</code> implementations that extend
+ <code>FormAuthenticator</code> and override
+ <code>doAuthenticate()</code> and/or <code>restoreRequest()</code> will
+ require modification. (markt)
+ </fix>
</changelog>
</subsection>
<subsection name="Coyote">
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]