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 95ab3576476..38422ed6e4b 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 @@ -68,21 +68,25 @@ public class FormAuthenticator extends LoginAuthenticator private String _formErrorPath; private String _formLoginPage; private String _formLoginPath; - private boolean _dispatch; private boolean _alwaysSaveUri; public FormAuthenticator() { } + @Deprecated public FormAuthenticator(String login, String error, boolean dispatch) + { + this(login, error); + } + + public FormAuthenticator(String login, String error) { this(); if (login != null) setLoginPage(login); if (error != null) setErrorPage(error); - _dispatch = dispatch; } /** @@ -113,8 +117,6 @@ public class FormAuthenticator extends LoginAuthenticator String error = configuration.getParameter(FormAuthenticator.__FORM_ERROR_PAGE); if (error != null) setErrorPage(error); - String dispatch = configuration.getParameter(FormAuthenticator.__FORM_DISPATCH); - _dispatch = dispatch == null ? _dispatch : Boolean.parseBoolean(dispatch); } @Override @@ -136,6 +138,11 @@ public class FormAuthenticator extends LoginAuthenticator _formLoginPath = _formLoginPath.substring(0, _formLoginPath.indexOf('?')); } + public String getLoginPage() + { + return _formLoginPage; + } + private void setErrorPage(String path) { if (path == null || path.trim().length() == 0) @@ -158,6 +165,11 @@ public class FormAuthenticator extends LoginAuthenticator } } + public String getErrorPage() + { + return _formErrorPage; + } + @Override public UserIdentity login(String username, Object password, Request request, Response response) { @@ -270,7 +282,8 @@ public class FormAuthenticator extends LoginAuthenticator final String password = parameters.getValue(__J_PASSWORD); UserIdentity user = login(username, password, request, response); - LOG.debug("jsecuritycheck {} {}", username, user); + if (LOG.isDebugEnabled()) + LOG.debug("jsecuritycheck {} {}", username, user); if (user != null) { // Redirect to original request @@ -285,11 +298,9 @@ public class FormAuthenticator extends LoginAuthenticator } // not authenticated - 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); - + if (LOG.isDebugEnabled()) + LOG.debug("auth failed {}=={}", username, _formErrorPage); + sendError(request, response, callback); return AuthenticationState.SEND_FAILURE; } @@ -313,7 +324,8 @@ public class FormAuthenticator extends LoginAuthenticator // if we can't send challenge if (response.isCommitted()) { - LOG.debug("auth deferred {}", session == null ? null : session.getId()); + if (LOG.isDebugEnabled()) + LOG.debug("auth deferred {}", session == null ? null : session.getId()); return null; } @@ -352,10 +364,23 @@ public class FormAuthenticator extends LoginAuthenticator // send the challenge if (LOG.isDebugEnabled()) LOG.debug("challenge {}->{}", session.getId(), _formLoginPage); - Response.sendRedirect(request, response, callback, encodeURL(URIUtil.addPaths(request.getContext().getContextPath(), _formLoginPage), request), true); + sendChallenge(request, response, callback); return AuthenticationState.CHALLENGE; } + protected void 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); + } + + protected void sendChallenge(Request request, Response response, Callback callback) + { + Response.sendRedirect(request, response, callback, encodeURL(URIUtil.addPaths(request.getContext().getContextPath(), _formLoginPage), request), true); + } + public boolean isJSecurityCheck(String uri) { int jsc = uri.indexOf(__J_SECURITY_CHECK); 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 e1a6a780c3a..3db5cea24c8 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 @@ -445,6 +445,20 @@ public class ServletChannel _written = 0; } + + public void dispatched(Dispatchable dispatchable) throws Exception + { + if (LOG.isDebugEnabled()) + LOG.debug("handle {} {} ", _servletContextRequest.getHttpURI(), this); + + Action action = _state.handling(); + if (action != Action.DISPATCH) + throw new IllegalStateException(action.name()); + + dispatchable.dispatch(); + + } + /** * Handle the servlet request. This is called on the initial dispatch and then again on any asynchronous events. * @return True if the channel is ready to continue handling (ie it is not suspended) @@ -889,7 +903,7 @@ public class ServletChannel } } - interface Dispatchable + public interface Dispatchable { void dispatch() throws Exception; } 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 new file mode 100644 index 00000000000..4e0132490bc --- /dev/null +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/security/FormAuthenticator.java @@ -0,0 +1,199 @@ +// +// ======================================================================== +// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.ee10.servlet.security; + +import java.util.Collections; +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; +import jakarta.servlet.http.HttpServletResponseWrapper; +import org.eclipse.jetty.ee10.servlet.ServletChannel; +import org.eclipse.jetty.ee10.servlet.ServletContextRequest; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpHeaderValue; +import org.eclipse.jetty.security.authentication.SessionAuthentication; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; + +/** + * 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 + * {@link SessionAuthentication} to wrap Authentication results so that they + * are associated with the session.
+ */ +public class FormAuthenticator extends org.eclipse.jetty.security.authentication.FormAuthenticator +{ + public static final String __FORM_DISPATCH = "org.eclipse.jetty.security.dispatch"; + + private boolean _dispatch; + + public FormAuthenticator() + { + } + + public FormAuthenticator(String login, String error, boolean dispatch) + { + super(login, error, dispatch); + _dispatch = dispatch; + } + + @Override + public void setConfiguration(Configuration configuration) + { + super.setConfiguration(configuration); + + String dispatch = configuration.getParameter(FormAuthenticator.__FORM_DISPATCH); + _dispatch = dispatch == null ? _dispatch : Boolean.parseBoolean(dispatch); + } + + @Override + protected void sendError(Request request, Response response, Callback callback) + { + if (_dispatch && getErrorPage() != null) + dispatch(getErrorPage(), request, response, callback); + else + super.sendError(request, response, callback); + } + + @Override + protected void sendChallenge(Request request, Response response, Callback callback) + { + if (_dispatch) + dispatch(getLoginPage(), request, response, callback); + else + super.sendChallenge(request, response, callback); + } + + private void 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())); + + // TODO: we need to run the ServletChannel. + callback.succeeded(); + } + catch (Throwable t) + { + Response.writeError(request, response, callback, t); + } + } + + protected static class FormRequest extends HttpServletRequestWrapper + { + public FormRequest(HttpServletRequest request) + { + super(request); + } + + @Override + public long getDateHeader(String name) + { + if (name.toLowerCase(Locale.ENGLISH).startsWith("if-")) + return -1; + return super.getDateHeader(name); + } + + @Override + public String getHeader(String name) + { + if (name.toLowerCase(Locale.ENGLISH).startsWith("if-")) + return null; + return super.getHeader(name); + } + + @Override + public Enumeration