users@glassfish.java.net

Login not "standard"

From: Per Steffensen <steff_at_designware.dk>
Date: Thu, 20 Aug 2009 13:46:56 +0200

Hi

Using Glassfish v2.1

I want to do a programmatic login on the server-side.

--------------- Questions/comments
----------------------------------------------
I will start with my questions/comments (not to have them i the bottom,
where no one sees them :-) ).
1) Why is Glassfish not made, so that it is possible to make
programmatic login the standard way?
2) Why does Glassfish invent a SecurityContext class to to hold the
current Subject, when that is supposed to be set by Subject.doAs
3) Glassfish, beeing the standard implementation, should implement
standard ways of doing things.

The rest of the mail will explain details, to make my questions/comments
qualified.

---------------- I have to do like this in Glassfish
------------------------------
I have found out that programmatic login will work (at least when I use
FileRealm and FileLoginModule) when I do like this:

login(username, password);
<call my business-login on secured EJBs>

where the login method looks something like this:

public static void login(String username, String password) throws
LoginException
    {
        try {
            Subject sub = new Subject();
            sub.getPrivateCredentials().add(new new
com.sun.enterprise.security.auth.login.PasswordCredential(username,
password, com.sun.enterprise.security.auth.realm.Realm.getDefaultRealm()));
            LoginContext lc = new LoginContext("fileRealm", sub);
            lc.login();
            com.sun.enterprise.security.SecurityContext securityContext
= new com.sun.enterprise.security.SecurityContext(username,
lc.getSubject(),
com.sun.enterprise.security.auth.realm.Realm.getDefaultRealm());
            
com.sun.enterprise.security.SecurityContext.setCurrent(securityContext);
        } catch (Exception e) {
            // do something
        }
    }

I know that I could simplify my login method by using the
ProgrammaticLogin class, but it will not change the existence of the
problems mentioned below.

------------------ Problems ------------------------------
This way of doing programmatic login is really non-standard. Glassfish
as the reference implementation should be working the standard way, I
think. My login-code should be able to be used (without changes) if I
choose to move to another Java EE server.
Problems:
- I have to use alot of Glassfish-specific classes
(com.sun.enterprise.security.auth.login.PasswordCredential,
com.sun.enterprise.security.SecurityContext and
com.sun.enterprise.security.auth.realm.Realm)
- You do not even have to use Subject.doAs
- My login code will not work on any other Java EE server.

---------------- The way I should be able to do it
---------------------------------
I should be able to do it the "right" (compatible) way on the Glassfish
server:

LoginContext lc = login(username, password);
Subject.doAs(lc.getSubject(), <call my business-login on secured EJBs
packed in a PrivilegedAction>);

Where the login method looks something like this:

    public static LoginContext login(String username, String password)
throws LoginException
    {
        try {
            LoginContext lc = new LoginContext("fileRealm", new
UserPasswordCallbackHandler(username, password));
            lc.login();
            return lc;
        } catch (Exception e) {
            // do something
        }
    }

Where UserPasswordCallbackHandler looks something like this:

public class UserPasswordCallbackHandler implements CallbackHandler {

    private String username;
    private String password;
   
    public UserPasswordCallbackHandler(String username, String password) {
        super();
        this.username = username;
        this.password = password;
    }

    public void handle(Callback[] callbacks) throws IOException,
            UnsupportedCallbackException {
        for (int i = 0; i < callbacks.length; i++) {
            if (callbacks[i] instanceof TextOutputCallback) {
                // display the message according to the specified type
                TextOutputCallback toc = (TextOutputCallback) callbacks[i];
                switch (toc.getMessageType()) {
                case TextOutputCallback.INFORMATION:
                    GeneralManager.log(toc.getMessage(), LogLevel.INFO);
                    break;
                case TextOutputCallback.ERROR:
                    GeneralManager.log(toc.getMessage(), LogLevel.ERROR);
                    break;
                case TextOutputCallback.WARNING:
                    GeneralManager.log(toc.getMessage(), LogLevel.WARNING);
                    break;
                default:
                    throw new IOException("Unsupported message type: "
                            + toc.getMessageType());
                }
            } else if (callbacks[i] instanceof NameCallback) {
                // prompt the user for a username
                NameCallback nc = (NameCallback) callbacks[i];

                // ignore the provided defaultName
                // System.err.print(nc.getPrompt());
                // System.err.flush();

                nc.setName(username);
            } else if (callbacks[i] instanceof PasswordCallback) {
                // prompt the user for sensitive information
                PasswordCallback pc = (PasswordCallback) callbacks[i];
                //System.err.print(pc.getPrompt());
                //System.err.flush();
                pc.setPassword(password.toCharArray());
            } else {
                throw new UnsupportedCallbackException(callbacks[i],
                        "Unrecognized Callback");
            }
        }
    }
}