custom ServerAuthModule (loginModule), where to hash the password? JASPIC

1.2k Views Asked by At

I'm trying to implement a loginmodule so I can perform a "remember me" feature on my web app as well as hash my password with bcrypt. To build the class I used this tutorial. However I didn't manage to connect after implementing this. The passwords in db are hashed via SHA-256 at the moment and I suspect it is the reason why.

  public class TestAuthModule implements
        javax.security.auth.message.module.ServerAuthModule {

    @SuppressWarnings("rawtypes")
    protected static final Class[] supportedMessageTypes = new Class[] {
            HttpServletRequest.class, HttpServletResponse.class };

    private CallbackHandler handler;

    public void initialize(MessagePolicy requestPolicy,
            MessagePolicy responsePolicy, CallbackHandler handler,
            @SuppressWarnings("rawtypes") Map options) throws AuthException {
        System.out.println("initialize called.");
        this.handler = handler;
    }

    @SuppressWarnings("rawtypes")
    public Class[] getSupportedMessageTypes() {
        return supportedMessageTypes;
    }

    public AuthStatus validateRequest(MessageInfo messageInfo,
            Subject clientSubject, Subject serverSubject) throws AuthException {
        HttpServletRequest request = (HttpServletRequest) messageInfo
                .getRequestMessage();

        String user = request.getParameter("user");
        String group = request.getParameter("group");

        System.out.println("validateRequest called.");
        System.out.println("User = " + user);
        System.out.println("Group = " + group);

        authenticateUser(user, group, clientSubject, serverSubject);

        return AuthStatus.SUCCESS;
    }

    public AuthStatus secureResponse(MessageInfo msgInfo, Subject service)
            throws AuthException {
        System.out.println("secureResponse called.");
        return AuthStatus.SEND_SUCCESS;
    }

    public void cleanSubject(MessageInfo msgInfo, Subject subject)
            throws AuthException {
        if (subject != null) {
            subject.getPrincipals().clear();
        }
    }

    private void authenticateUser(String user, String group,
            Subject clientSubject, Subject serverSubject) {
        System.out
                .println("Authenticating user " + user + " in group " + group);

        CallerPrincipalCallback callerPrincipalCallback = new CallerPrincipalCallback(
                clientSubject, user);

        GroupPrincipalCallback groupPrincipalCallback = new GroupPrincipalCallback(
                clientSubject, new String[] { group });

        try {
            handler.handle(new Callback[] { callerPrincipalCallback,
                    groupPrincipalCallback });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

and I login like this (which did work before implementing a custom loginmodule):

private String username;
private Password password;
//....

for (int i = 0; i < x -1 ; i++) {
    this.password = PasswordEncoder
                       .toHex(PasswordEncoder
                            .hash512(this.password + salt));
    }
   // x is the number of time I hashed the password before storing it in db.
   // x-1 because glassfish authentication does it once for me.

   //...
try {
    request.login(username, password + salt);
    } catch (ServletException e)

Also on my pages I used to have a register and a sign in button that were displayed only if the user was null if not I had the username at the top. Now that I implemented this it's like the user is connected as "ANONYMOUS" (so there is "you are connected as ANONYMOUS" at the top of the page. To prevent this I did a temporary fix:

    if (username == null || username.equals("ANONYMOUS")) {
        this.isUserConnected = false;
    } else {
        this.isUserConnected = true;
    }

I tried :

isUserInGroup("ANONYMOUS"); 

but there is no user so I'm getting a npe. I'm not sure how to go about this as well.

1

There are 1 best solutions below

0
On BEST ANSWER

There are two alternative approaches you can employ here.

The first --which I personally favor-- would be to just forget about proprietary AS-provided JAAS LoginModules (LMs) altogether and implement the authentication process in its entirety yourself, within your ServerAuthModule's (SAM's) validateRequest method. This gives you the ultimate freedom of choice with regard to how credentials are to be persisted (hashing/salting, DB schema) by the application and how the SAM shall perform validation of client-supplied credentials. Hence, the SAM is responsible for connecting to the DB in both a thread-safe and efficient manner in that case; that would be the sole potentially tricky aspect of this approach.

Alternatively, your SAM can delegate credential validation to an AS-provided LM (or to one you authored, provided that it is applicable, i.e. a vendor-specific extension). That's what you've been trying to accomplish, I suppose. The SAM must then comply with both JASPIC's Servlet Container Profile and its LoginModule Bridge Profile (see chapter 6 of the spec); the latter, in a nutshell, mandates that:

  1. The SAM's initialize method consults the value of the javax.security.auth.login.LoginContext key encapsulated within the runtime-provided options. It also constructs an additional CallbackHandler that supports NameCallback and PasswordValidationCallback. Finally, it instantiates a request-specific LoginContext (LC) with the aforementioned instances (name and callbackHandler arguments, respectively).
  2. Its validateRequest delegates to LC's login; then communicates any Principal(s) established by the underlying LM within LC's Subject to the AS (via CallerPrincipalCallback and GroupPrincipalCallback).
  3. Its cleanSubject delegates to LC's logout.

Obviously, unless you wrote the LM yourself, no clean way of altering the credential validation process is available.

Some closing notes:

  • HttpServletRequest.login will always raise an exception when JASPIC has been configured for use with your application (see § 3.9.2 of the spec). Use HttpServletRequest.authenticate instead.
  • Your SAM's validateRequest should return a proper AuthStatus; namely SUCCESS when login succeeded or authentication is optional (i.e. the requested resource is unprotected), regardless of the authentication outcome; SEND_CONTINUE when redirecting the user to a login page; SEND_FAILURE (or throw an AuthException) when login failed and authentication is mandatory (i.e. the requested resource is protected or the user explicitly requested to be logged in).
  • The identity of the unauthenticated user should be communicated by your SAM's validateRequest to the AS by supplying the CallerPrincipalCallback/GroupPrincipalCallback constructor with a null principal or groups argument, respectively. A compatible Servlet container's HttpServletRequest.getUserPrincipal's implementation should then return null. Note that EJBContext.getCallerPrincipal behaves differently, returning an instance of an AS-specific type in order to represent the unauthenticated user (perhaps that's what you witnessed). In any case, your authorization-related application-level code should not depend on the non-standard name of the anonymous Principal. An alternative way of addressing the is-guest question could involve setting the javax.servlet.http.authType callback property (see § 3.8.4 of the spec) from your SAM on successful authentication. Invoking HttpServletRequest.getAuthType would then not only assert whether the user is authenticated, but also whether your SAM, another SAM or AS-specific LM performed the authentication (think of an app leveraging multiple authentication types). Yet another way of dealing with this could be by passing a custom Principal implementation to the CallerPrincipalCallback; then doing an instanceof on the Principal exposed via HttpServletRequest.getUserPrincipal (but note that there are still issues, e.g. on GlassFish an EJB is needed to achieve the same result).

See also: