Search code examples
tomcatservletsundertow

Does Undertow have an equivalent to Tomcat WebAuthentication for authenticating in web tier?


This may be an instance of an "XY problem" question; if it is, feel free to answer by correcting my assumptions.


Update: At one point, I had tried to use the HttpServletRequest by doing loginRequest.login(username, password);, but that would not compile, yielding compile errors about login(String, String) not being found in HttpServletRequest, so I abandoned that angle. However, I just found an article which suggests that this is the correct change to make, so I am back on that trail again.

I had someone tell me that perhaps I was using an old version of the class. It is possible that I have the old HttpServletRequest on my compile-time class-path. I am looking into that now.


After switching from Tomcat (in JBoss 5) to Undertow (in JBoss 7), our user authentication page is broken.

We have an HttpServlet which used to run in Tomcat web server in JBoss 5. As such, it uses Tomcat's WebAuthentication like so:

import org.jboss.web.tomcat.security.login.WebAuthentication;
import java.io.IOException;
import javax.servlet.http.*;
// ... import etc.

public class MyHttpServlet extends HttpServlet
{
    private void loginCase( HttpServletRequest loginRequest,
        HttpServletResponse loginResponse ) throws ServletException, IOException, RemoteException
    {
        String username;
        String password;

        // ... other stuff

        // Authenticate with the web tier. This invokes the MyLoginModule,
        // which uses the user database tables to authenticate and establish
        // the user's role(s). This servlet does role checking, but this step
        // is necessary to get the user's credentials into the container, which
        // secures remote EJBs. If this is not done, the servlets and JSPs will
        // not be able to properly access secured EJBs.
        WebAuthentication webAuth = new WebAuthentication();
        boolean loginResult = webAuth.login(username, password);

        // ...
    }
}

The comment immediately preceeding the use of WebAuthentication, the one about authenticating with web tier, was not added by me. That is in the original code, and looks like it is an explanation about why WebAuthentication was chosen as the mechanism for authenticating.

My understanding is that Undertow/JBoss7 no longer has the WebAuthentication class available.

At runtime, when a user submits their name/password entry, there is a ClassNotFoundException with message org.jboss.web.tomcat.security.login.WebAuthentication from [module "deployment.myproject.ear.viewers.war" from Service Module Loader]

Now that we no longer have access to WebAuthentication (I even copied jar files from Tomcat containing the necessary classes over into my .ear file's lib/ directory, but Undertow did not seem to appreciate that and I got different errors), I am looking for a replacement.

My understanding is that I just need to cause the user to authenticate via the web tier, essentially a replacement for WebAuthentication.login. The only uses of WebAuthentication are the two lines mentioned in the above code sample.

What can I replace WebAuthentication with to make this work?

Obviously, a simple class replacement would be awesome, but if it must be more complicated than that, then so be it as long as we can get this working.


Solution

  • In general the management of users is left out of the JEE spec so every vendor does it a different way. Take a look at the docs for DatabaseServerLoginModule that is built into JBoss/Wildfly. If that's not sufficient (and I dislike the table layout it uses) another way is to write your own:

    ...
    
    import org.jboss.security.SimpleGroup;
    import org.jboss.security.SimplePrincipal;
    import org.jboss.security.auth.spi.UsernamePasswordLoginModule;
    
    ...
    
    /**
     * Extends a WildFly specific class to implement a custom login module.
     * 
     * Note that this class is referenced in the standalone.xml in a 
     * security-domain/authentication/login-module section.  If the package or name
     * changes then that needs to be updated too.
     * 
     */
    public class MyLoginModule extends UsernamePasswordLoginModule {
        private MyPrincipal principal;
    
        @Override
        public void initialize(Subject subject, CallbackHandler callbackHandler,
                Map<String, ?> sharedState, Map<String, ?> options) {
    
            super.initialize(subject, callbackHandler, sharedState, options);
        }
    
        /**
         * While we have to override this method the validatePassword method ignores
         * the value.
         */
        @Override
        protected String getUsersPassword() throws LoginException {
            return null;
        }
    
        /**
         * Validates the password passed in (inputPassword) for the given username.
         */
        @Override
        protected boolean validatePassword(String inputPassword, String expectedPassword) {
            // validate as you do today - IGNORE "expectedPassword"
        }
    
        /**
         * Get the roles that are tied to the user.
         */
        @Override
        protected Group[] getRoleSets() throws LoginException {
            SimpleGroup group = new SimpleGroup("Roles");
    
            List<String> userRoles = // get roles for a user the way you do currently
    
            for( String nextRoleName: userRoles ) {
                group.addMember(new SimplePrincipal(nextRoleName));
            }
    
            return new Group[] { group };
        }
    
        /**
         * This method is what ends up triggering the other methods.
         */
        @Override
        public boolean login() throws LoginException {
            boolean login;
    
            login = super.login();
            if (login) {
                principal = // populate the principal
    
            return login;
        }
    
        @Override
        protected Principal getIdentity() {
            return principal != null ? principal : super.getIdentity();
        }
    }
    

    This brings you much closer to standard JEE security. Your method won't even be called if it's not allowed (based on the security-constraint stanzas in your web.xml) and you don't have to validate that the user is logged in.

    It's a bit daunting at first but it's pretty straight forward once it works.

    Having said all that, I've since removed code like this and moved to Keycloak. This is a separate authorization engine that also integrates with Tomcat. It has a huge number of features like social login and so on.