4
votes

My web application uses internal web API (simple AJAX requests from a browser as it's the major client) that's eventually supposed to be exposed externally for the 3rd parties. Since the API is and must be protected with the security constraints in web.xml, a user or a client must be authenticated. Currently, there's a custom form authenticator implemented, it performs some additional checks and actions, and then simply delegates the further authentication processing to the FormAuthenticator class the custom authenticator is derived from. This works really nice yet, because a user is simply forced to login and pass authentication, and the only client is a web browser.

But FORM authentication is not very suitable for another kinds of clients: let's say an Android client, various 3rd-party clients, etc. Too pass the form authentication they all must simulate the behavior I was looking for in this question: How can I simulate form authentication in Tomcat using JMeter? . After some investigation of the Tomcat source code, I've got an idea that it should be possible to extend the AuthenticatorBase class to implement own way of authentication (like FormAuthenticator or BasicAuthenticator.java do). What I tried to do was:

public final class SimpleAuthenticator extends AuthenticatorBase {

    private static final String USERNAME_PARAMETER = "username";
    private static final String PASSWORD_PARAMETER = "password";

    @Override
    protected boolean authenticate(Request request, Response response, LoginConfig config) throws IOException {
        final Principal principal = request.getUserPrincipal();
        final String ssoId = (String) request.getNote(REQ_SSOID_NOTE);
        if ( principal != null ) {
            if ( ssoId != null ) {
                associate(ssoId, request.getSessionInternal(true));
            }
            return true;
        }
        if ( ssoId != null && reauthenticateFromSSO(ssoId, request) ) {
            return true;
        }
        final String contextPath = request.getContextPath();
        final String requestURI = request.getDecodedRequestURI();
        final boolean login = requestURI.equals(contextPath + "/authenticate");
        if ( !login ) {
            response.sendError(SC_FORBIDDEN);
            return false;
        }
        final String username = request.getParameter(USERNAME_PARAMETER);
        final String password = request.getParameter(PASSWORD_PARAMETER);
        final Realm realm = context.getRealm();
        final Principal authenticatedUserPrincipal = realm.authenticate(username, password);
        if ( authenticatedUserPrincipal == null ) {
            response.sendError(SC_UNAUTHORIZED);
            return false;
        }
        register(request, response, authenticatedUserPrincipal, "SIMPLE", username, password);
        return true;
    }

}

Simply speaking, I'd like to use something like /%CONTEXT_PATH%/authenticate?username=%USERNAME%&password=%PASSWORD% to authenticate a user with my custom non-form SimpleAuthenticator. Not really sure if BasicAuthentication would be better for such a case, but I got the following issues with the example above:

  • Tomcat allows to specify multiple Valve's in context.xml. If the another authenticator is added to the context.xml file, then every secured resource is processed with every authenticator. (In principle, I understand why it happens, but can they be separated for different resources?)
  • /%CONTEXT_PATH%/authenticate is not accessible (HTTP 404). (Not clear yet knowing that /j_security_check is simulated somehow.)
  • I couldn't find a way to specify multiple authentication schemes in Tomcat, so "old" web browser client could still use FormAuthenticator (like it does today), but the "light-weight" clients could use that simplified authentication I tried to implement with SimpleAuthenticator. (Don't even know if it's possible -- that is the core)
  • As far as I understood the servlet specification, only one login-config is allowed for entire web-application. (Well, should I have use another web app to provide the API?)
  • I saw some mentions to implement custom authentication via Filter, but if it's possible -- I'd like to keep the authenticator module separately in a single place (like it already is and confirmed with Tomcat: Custom form authenicator in a web application, not as a stand-alone JAR module. Possible? ). However I'm very ok to review the general concept I use from scratch.

I suspect that I'm doing a quite wrong thing and have total understanding, but I don't believe that there's no way to implement multiple authentication schemes in Tomcat.

Is there any way to provide multiple authentication schemes to extract the authentication out of FormAuthetication (for light-weight clients)? Your help would be really very appreciated. Thanks in advance.

1
This limitation drives me crazy tooDavid Brossard

1 Answers

2
votes

The bottom line: You cannot configure multiple authentication schemes for the same web application in Tomcat. You configure one auth-method that will be used for the authentication in the web.xml:

<login-config>
    <auth-method>CLIENT-CERT</auth-method>
</login-config>

Tomcat map on the authenticator for the web application authentication. For example, for the CLIENT-CERT it will use org.apache.catalina.authenticator.SSLAuthenticator The mapping is in the file org/apache/catalina/startup/Authenticators.properties

For purpose you describe in the question you may use regular Tomcat Form Authenticator (<auth-method>FORM</auth-method>)

The following code will authenticate a user:

String url = "j_security_check?j_username=" + username + "&j_password=" + password;
String redirectUrl = response.encodeRedirectURL(url);
response.sendRedirect(redirectUrl);

When you first access application authenticate a user by execution URL above. Also, you can use Form Authenticator for “old” code :) Please note, the code above uses GET method. I recommend to submit same parameters in POST method to j_security_check (you can do it in AJAX).