Created attachment 32744 [details] Result of a diff -u operated on the original code and the pseudo-code containing a 2fa workaround Hi there, for an application to be deployed on a TomEE we need the support for two factor authentication. Are there planned development efforts going into that direction? As temporary workaround we patched the class FormAuthenticator, so that a second factor (a 2fa token) is additionlly evaluated. This patch is in the style of AuthenticRoast, an solution available for Tomcat 1.6. Here the code patch in pseudo code: package org.apache.catalina.authenticator; [original code omitted with imports] /** * An <b>Authenticator</b> and <b>Valve</b> implementation of FORM BASED Authentication, as * described in the Servlet API Specification, Version 2.2. */ public class FormAuthenticator extends AuthenticatorBase { [original code omitted] // --------------------------------------------------------- Public 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 * @param config Login configuration describing how authentication should be performed * * @exception IOException if an input/output error occurs */ @Override public boolean authenticate(Request request, HttpServletResponse response, LoginConfig config) throws IOException { // References to objects we will need later Session session = null; // Have we already authenticated someone? Principal principal = request.getUserPrincipal(); String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE); [original code omitted] // Yes -- Acknowledge the request, validate the specified credentials // and redirect to the error page if they are not correct request.getResponse().sendAcknowledgement(); Realm realm = context.getRealm(); if (characterEncoding != null) { request.setCharacterEncoding(characterEncoding); } String username = request.getParameter(Constants.FORM_USERNAME); String password = request.getParameter(Constants.FORM_PASSWORD); if (log.isDebugEnabled()) { log.debug("Authenticating username '" + username + "'"); } // BEGIN --- Customized code for two factor authentication Part I --- BEGIN String password2factor = request.getParameter("j_2fa"); // A customized Realm Class which implements a second factor authentication final TwoFactorRealm twoFactorRealm = new TwoFactorRealm(...); boolean twoFactorSuccessful = twoFactorRealm.authenticate(username, password2factor); if (log.isWarnEnabled() && !twoFactorSuccessful) { log.warn("Authentication 2FA failed for user " + username); } // END --- Customized code for two factor authentication Part I --- END principal = realm.authenticate(username, password); // BEGIN --- Customized code for two factor authentication Part II --- BEGIN if (principal == null || !twoFactorSuccessful) { log.warn("Authentication of '" + username + "' failed: [principal=" + principal + "], [twoFactorSuccessful=" + twoFactorSuccessful + "]"); forwardToErrorPage(request, response, config); return (false); } // END --- Customized code for two factor authentication Part II --- END if (log.isDebugEnabled()) { log.debug("Authentication of '" + username + "' was successful"); } if (session == null) { session = request.getSessionInternal(false); } if (session == null) { if (containerLog.isDebugEnabled()) { containerLog.debug("User took so long to log on the session expired"); } if (landingPage == null) { response.sendError(HttpServletResponse.SC_REQUEST_TIMEOUT, sm.getString("authenticator.sessionExpired")); } else { // Make the authenticator think the user originally requested // the landing page String uri = request.getContextPath() + landingPage; SavedRequest saved = new SavedRequest(); saved.setMethod("GET"); saved.setRequestURI(uri); saved.setDecodedRequestURI(uri); request.getSessionInternal(true).setNote(Constants.FORM_REQUEST_NOTE, saved); response.sendRedirect(response.encodeRedirectURL(uri)); } return (false); } // Save the authenticated Principal in our session session.setNote(Constants.FORM_PRINCIPAL_NOTE, principal); // Save the username and password as well session.setNote(Constants.SESS_USERNAME_NOTE, username); session.setNote(Constants.SESS_PASSWORD_NOTE, password); // Redirect the user to the original request URI (which will cause // the original request to be restored) requestURI = savedRequestURL(session); if (log.isDebugEnabled()) { log.debug("Redirecting to original '" + requestURI + "'"); } if (requestURI == null) { if (landingPage == null) { response.sendError(HttpServletResponse.SC_BAD_REQUEST,sm.getString("authenticator.formlogin")); } else { // Make the authenticator think the user originally requested // the landing page String uri = request.getContextPath() + landingPage; SavedRequest saved = new SavedRequest(); saved.setMethod("GET"); saved.setRequestURI(uri); saved.setDecodedRequestURI(uri); session.setNote(Constants.FORM_REQUEST_NOTE, saved); response.sendRedirect(response.encodeRedirectURL(uri)); } } else { response.sendRedirect(response.encodeRedirectURL(requestURI)); } return (false); } [original code omitted] } Best regards, Marco -- Dipl.-Inf. Marco Bellavia Software Engineering DENIC eG Kaiserstraße 75-77 60329 Frankfurt am Main GERMANY Angaben nach § 25a Absatz 1 GenG: DENIC eG (Sitz: Frankfurt am Main) Vorstand: Helga Krüger, Andreas Musielak, Carsten Schiefner, Dr. Jörg Schweiger Vorsitzender des Aufsichtsrats: Thomas Keller Eingetragen unter Nr. 770 im Genossenschaftsregister, Amtsgericht Frankfurt am Main
I'm not s big fan of the current implementation as a patch to FormAuthenticator. Why not do this as a subclass?
The patch is incomplete - it does not include the Realm changes necessary. This patch implements one view of how this could be done. It is not implementing a standard. This could be implemented as a JASPIC module (or even with JAAS). Given all of the above, I don't think this should be included in the standard Tomcat distribution. If a plug-in module was developed to implement this, we could add a pointer to it on the wiki.