diff --git a/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/AuthenticationState.java b/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/AuthenticationState.java index 11e40aef0658..587294b553a5 100644 --- a/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/AuthenticationState.java +++ b/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/AuthenticationState.java @@ -270,6 +270,18 @@ public String toString() } }; + /** + * Authentication should be deferred, and request allowed to bypass security constraint. + */ + AuthenticationState DEFER = new AuthenticationState() + { + @Override + public String toString() + { + return "DEFER"; + } + }; + static Deferred defer(LoginAuthenticator loginAuthenticator) { return new DeferredAuthenticationState(loginAuthenticator); diff --git a/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java b/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java index 584b235041bb..cc7a754003ef 100644 --- a/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java +++ b/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java @@ -495,7 +495,7 @@ public boolean handle(Request request, Response response, Callback callback) thr return true; } - if (authenticationState == null) + if (authenticationState == null || authenticationState == AuthenticationState.DEFER) authenticationState = _deferred; AuthenticationState.setAuthenticationState(request, authenticationState); @@ -503,7 +503,6 @@ public boolean handle(Request request, Response response, Callback callback) thr (authenticationState instanceof AuthenticationState.Succeeded user) ? _identityService.associate(user.getUserIdentity(), null) : null; - try { //process the request by other handlers @@ -556,8 +555,10 @@ protected void redirectToSecure(Request request, Response response, Callback cal protected boolean isNotAuthorized(Constraint constraint, AuthenticationState authenticationState) { - UserIdentity userIdentity = authenticationState instanceof AuthenticationState.Succeeded user ? user.getUserIdentity() : null; + if (authenticationState == AuthenticationState.DEFER) + return false; + UserIdentity userIdentity = authenticationState instanceof AuthenticationState.Succeeded user ? user.getUserIdentity() : null; return switch (constraint.getAuthorization()) { case FORBIDDEN, ALLOWED, INHERIT -> false; diff --git a/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java b/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java index 38422ed6e4b5..5c740534e207 100644 --- a/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java +++ b/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java @@ -40,10 +40,6 @@ /** * FORM Authenticator. * - *

This authenticator implements form authentication will use dispatchers to - * the login page if the {@link #__FORM_DISPATCH} init parameter is set to true. - * Otherwise it will redirect.

- * *

The form authenticator redirects unauthenticated requests to a log page * which should use a form to gather username/password from the user and send them * to the /j_security_check URI within the context. FormAuthentication uses @@ -56,7 +52,6 @@ public class FormAuthenticator extends LoginAuthenticator public static final String __FORM_LOGIN_PAGE = "org.eclipse.jetty.security.form_login_page"; public static final String __FORM_ERROR_PAGE = "org.eclipse.jetty.security.form_error_page"; - public static final String __FORM_DISPATCH = "org.eclipse.jetty.security.dispatch"; public static final String __J_URI = "org.eclipse.jetty.security.form_URI"; public static final String __J_POST = "org.eclipse.jetty.security.form_POST"; public static final String __J_METHOD = "org.eclipse.jetty.security.form_METHOD"; @@ -300,8 +295,7 @@ public AuthenticationState validateRequest(Request request, Response response, C // not authenticated if (LOG.isDebugEnabled()) LOG.debug("auth failed {}=={}", username, _formErrorPage); - sendError(request, response, callback); - return AuthenticationState.SEND_FAILURE; + return sendError(request, response, callback); } // Look for cached authentication @@ -364,21 +358,22 @@ public AuthenticationState validateRequest(Request request, Response response, C // send the challenge if (LOG.isDebugEnabled()) LOG.debug("challenge {}->{}", session.getId(), _formLoginPage); - sendChallenge(request, response, callback); - return AuthenticationState.CHALLENGE; + return sendChallenge(request, response, callback); } - protected void sendError(Request request, Response response, Callback callback) + protected AuthenticationState sendError(Request request, Response response, Callback callback) { if (_formErrorPage == null) Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403); else Response.sendRedirect(request, response, callback, encodeURL(URIUtil.addPaths(request.getContext().getContextPath(), _formErrorPage), request), true); + return AuthenticationState.SEND_FAILURE; } - protected void sendChallenge(Request request, Response response, Callback callback) + protected AuthenticationState sendChallenge(Request request, Response response, Callback callback) { Response.sendRedirect(request, response, callback, encodeURL(URIUtil.addPaths(request.getContext().getContextPath(), _formLoginPage), request), true); + return AuthenticationState.SEND_FAILURE; } public boolean isJSecurityCheck(String uri) diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannel.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannel.java index 3db5cea24c82..a065cfb857a3 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannel.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannel.java @@ -20,6 +20,8 @@ import java.util.concurrent.atomic.AtomicLong; import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.ee10.servlet.ServletRequestState.Action; import org.eclipse.jetty.http.BadMessageException; import org.eclipse.jetty.http.HttpFields; @@ -71,6 +73,9 @@ public class ServletChannel { private static final Logger LOG = LoggerFactory.getLogger(ServletChannel.class); + public static final String INITIAL_DISPATCH_REQUEST = "jetty.initial.dispatch.request"; + public static final String INITIAL_DISPATCH_RESPONSE = "jetty.initial.dispatch.response"; + public static final String INITIAL_DISPATCH_PATH = "jetty.initial.dispatch.path"; private final ServletRequestState _state; private final ServletContextHandler.ServletScopedContext _context; @@ -445,18 +450,18 @@ private void recycle() _written = 0; } - - public void dispatched(Dispatchable dispatchable) throws Exception + /** + *

When this is called the initial dispatch will use the {@link ServletChannel#INITIAL_DISPATCH_PATH}, + * {@link ServletChannel#INITIAL_DISPATCH_REQUEST} and {@link ServletChannel#INITIAL_DISPATCH_RESPONSE} attributes + * to do a {@link jakarta.servlet.DispatcherType#FORWARD} dispatch instead of the initial + * {@link jakarta.servlet.DispatcherType#REQUEST} dispatch.

+ * + *

This must only be called before {@link ServletChannel#handle()} is first invoked. + * This can be used to dispatch to a different target before the initial request has been dispatched.

+ */ + public void initialDispatch() { - if (LOG.isDebugEnabled()) - LOG.debug("handle {} {} ", _servletContextRequest.getHttpURI(), this); - - Action action = _state.handling(); - if (action != Action.DISPATCH) - throw new IllegalStateException(action.name()); - - dispatchable.dispatch(); - + _state.initialDispatch(); } /** @@ -498,6 +503,19 @@ public boolean handle() break; } + case INITIAL_DISPATCH: + { + HttpServletRequest request = (HttpServletRequest)_request.removeAttribute(INITIAL_DISPATCH_REQUEST); + HttpServletResponse response = (HttpServletResponse)_request.removeAttribute(INITIAL_DISPATCH_RESPONSE); + String path = (String)_request.removeAttribute(INITIAL_DISPATCH_PATH); + + // TODO: Use listeners etc... + RequestDispatcher requestDispatcher = request.getRequestDispatcher(path); + requestDispatcher.forward(request, response); + + break; + } + case ASYNC_DISPATCH: { dispatch(_asyncDispatchable); @@ -903,7 +921,7 @@ public void abort(Throwable failure) } } - public interface Dispatchable + private interface Dispatchable { void dispatch() throws Exception; } diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletRequestState.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletRequestState.java index 937adbc0e6d7..9f3a9686aa2a 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletRequestState.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletRequestState.java @@ -62,6 +62,7 @@ public class ServletRequestState public enum State { IDLE, // Idle request + INITIAL_DISPATCH, // initialDispatch() has been called. HANDLING, // Request dispatched to filter/servlet or Async IO callback WAITING, // Suspended and waiting WOKEN, // Dispatch to handle from ASYNC_WAIT @@ -124,6 +125,7 @@ private enum OutputState public enum Action { DISPATCH, // handle a normal request dispatch + INITIAL_DISPATCH, // initial request will be forwarded ASYNC_DISPATCH, // handle an async request dispatch SEND_ERROR, // Generate an error page or error dispatch ASYNC_ERROR, // handle an async error @@ -322,6 +324,21 @@ public boolean abortResponse() } } + public void initialDispatch() + { + try (AutoLock ignored = lock()) + { + switch (_state) + { + case IDLE: + _state = State.INITIAL_DISPATCH; + break; + default: + throw new IllegalStateException(getStatusStringLocked()); + } + } + } + /** * @return Next handling of the request should proceed */ @@ -341,6 +358,13 @@ public Action handling() _state = State.HANDLING; return Action.DISPATCH; + case INITIAL_DISPATCH: + if (_requestState != RequestState.BLOCKING) + throw new IllegalStateException(getStatusStringLocked()); + _initial = true; + _state = State.HANDLING; + return Action.INITIAL_DISPATCH; + case WOKEN: if (_event != null && _event.getThrowable() != null && !_sendError) { diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/security/FormAuthenticator.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/security/FormAuthenticator.java index 4e0132490bcb..b4cbe6c450a3 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/security/FormAuthenticator.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/security/FormAuthenticator.java @@ -17,7 +17,6 @@ import java.util.Enumeration; import java.util.Locale; -import jakarta.servlet.RequestDispatcher; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; import jakarta.servlet.http.HttpServletResponse; @@ -26,6 +25,7 @@ import org.eclipse.jetty.ee10.servlet.ServletContextRequest; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; +import org.eclipse.jetty.security.AuthenticationState; import org.eclipse.jetty.security.authentication.SessionAuthentication; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; @@ -70,44 +70,42 @@ public void setConfiguration(Configuration configuration) } @Override - protected void sendError(Request request, Response response, Callback callback) + protected AuthenticationState sendError(Request request, Response response, Callback callback) { if (_dispatch && getErrorPage() != null) - dispatch(getErrorPage(), request, response, callback); + return dispatch(getErrorPage(), request, response, callback); else - super.sendError(request, response, callback); + return super.sendError(request, response, callback); } @Override - protected void sendChallenge(Request request, Response response, Callback callback) + protected AuthenticationState sendChallenge(Request request, Response response, Callback callback) { if (_dispatch) - dispatch(getLoginPage(), request, response, callback); + return dispatch(getLoginPage(), request, response, callback); else - super.sendChallenge(request, response, callback); + return super.sendChallenge(request, response, callback); } - private void dispatch(String path, Request request, Response response, Callback callback) + private AuthenticationState dispatch(String path, Request request, Response response, Callback callback) { try { - // We are before the ServletHandler, so we must do the association. - ServletChannel servletChannel = Request.get(request, ServletContextRequest.class, ServletContextRequest::getServletChannel); - servletChannel.associate(request, response, callback); - response.getHeaders().put(HttpHeader.CACHE_CONTROL.asString(), HttpHeaderValue.NO_CACHE.asString()); response.getHeaders().putDate(HttpHeader.EXPIRES.asString(), 1); ServletContextRequest contextRequest = Request.as(request, ServletContextRequest.class); - RequestDispatcher dispatcher = contextRequest.getServletApiRequest().getRequestDispatcher(path); - dispatcher.forward(new FormRequest(contextRequest.getServletApiRequest()), new FormResponse(contextRequest.getHttpServletResponse())); + contextRequest.setAttribute(ServletChannel.INITIAL_DISPATCH_PATH, path); + contextRequest.setAttribute(ServletChannel.INITIAL_DISPATCH_REQUEST, new FormRequest(contextRequest.getServletApiRequest())); + contextRequest.setAttribute(ServletChannel.INITIAL_DISPATCH_RESPONSE, new FormResponse(contextRequest.getHttpServletResponse())); + contextRequest.getServletChannel().initialDispatch(); - // TODO: we need to run the ServletChannel. - callback.succeeded(); + return AuthenticationState.DEFER; } catch (Throwable t) { Response.writeError(request, response, callback, t); + return AuthenticationState.SEND_FAILURE; } }