Search code examples
javaauthenticationjdbcjaas

Jaas JDBC Authentication


Our project consists of a JavaFX app who calls the JEE server by RMI protocol. We use the Payara app server, a Glassfish fork. We want to use JAAS with a database to manage authentication and permissions granted to a user.

We do not understand how to bind the created JDBC realm with our application to interact with a database.

Can we programmatically call the realm or directly query the database as here ?

The service starting the LoginContext :

@Stateless
public class AuthenticationService implements IAuthenticationService {
    @Override
    public boolean login(User user) {
        try {
            LoginContext lc = new LoginContext(
                "JDBCLoginModule",
                new JDBCCallbackHandler(user.getUsername(), user.getPassword())
            );

            lc.login();

            Subject subject = lc.getSubject();

            return true;

        } catch (LoginException ex) {
            ex.printStacktrace();
        }
        return false;
    }
}

The LoginModule :

public class JDBCLoginModule implements LoginModule {
    private Subject subject;
    private CallbackHandler callbackHandler;
    private Map sharedState;
    private Map options;

    private boolean succeeded = false;

    @Override
    public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
        this.subject            = subject;
        this.callbackHandler    = callbackHandler;
        this.sharedState        = sharedState;
        this.options            = options;
        succeeded               = false;
    }

    @Override
    public boolean login() throws LoginException {
        if (callbackHandler == null)
            throw new LoginException("The callbackHandler is null");

        Callback[] callbacks = new Callback[2];
        callbacks[0] = new NameCallback("name:");
        callbacks[1] = new PasswordCallback("password:", false);

        try {
            callbackHandler.handle(callbacks);
        } catch (IOException e) {
            throw new LoginException("IOException calling handle on callbackHandler");
        }
        catch (UnsupportedCallbackException e) {
            throw new LoginException("UnsupportedCallbackException calling handle on callbackHandler");
        }

        NameCallback nameCallback           = (NameCallback) callbacks[0];
        PasswordCallback passwordCallback   = (PasswordCallback) callbacks[1];

        String name = nameCallback.getName();
        String password = new String(passwordCallback.getPassword());

        // Call the JDBC Realm
        /*if ("myName".equals(name) && "myPassword".equals(password)) {
            succeeded = true;
            return succeeded;
        }
        else {
            succeeded = false;
            throw new FailedLoginException("Sorry! No login for you.");
        }*/
    }

    @Override
    public boolean commit() throws LoginException {
        return succeeded;
    }

    @Override
    public boolean abort() throws LoginException {
        return false;
    }

    @Override
    public boolean logout() throws LoginException {
        return false;
    }
}

The CallBackHandler :

public class JDBCCallbackHandler implements CallbackHandler {
    private final String username;
    private final String password;

    public JDBCCallbackHandler(String username, String password) {
        this.username = username;
        this.password = password;
    }

    @Override
    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
        for (int i = 0; i < callbacks.length; i++) {
            if (callbacks[i] instanceof NameCallback) {
                NameCallback nameCallback = (NameCallback) callbacks[i];
                nameCallback.setName(username);
            }
            else if (callbacks[i] instanceof PasswordCallback) {
                PasswordCallback passwordCallback = (PasswordCallback) callbacks[i];
                passwordCallback.setPassword(password.toCharArray());
            }
            else {
                throw new UnsupportedCallbackException(callbacks[i], "The submitted Callback is unsupported");
            }
        }
    }
}

We created a JDBC realm on the app server: JDBC Realm Image


Solution

  • If you expected that by calling AuthenticationService#login you'll be logged-in (authenticated) for the Java EE server (Payara), then this is absolutely the wrong approach.

    After the call to lc.login(); the Java EE server is not in any way aware of that call. A random LoginContext that you instantiate with the new operator does not magically become connected to the Java EE environment.

    If you're making a remote EJB call, you need to provide the authentication data with that call (from the JavaFx application) and then configure authentication for (remote) EJB in a GlassFish/Payara specific way. Contrary to Servlets, there is no standard way in Java EE to authenticate for EJB (for Servlets you'd be using JASPIC for this).

    There's also no such thing as a standard LoginModule. GlassFish does uses the LoginModule interface, but in a highly GlassFish specific way. If you only want to use the JDBC realm (realm is another term for "identity store" here, which is another term for "login module") you 'only' need to configure this using the glassfish specificglassfish-ejb-jar.xml file. Unfortunately, almost nobody knows how to do this exactly, but I think you need to look into the ior-security-config and research from there.

    Alternatively, you may be able to use the ProgrammaticLogin class, which is the GlassFish specific EJB equivalent to the standard HttpServletRequest#login.

    See Java EE 6 Application Client login