Using OpenSSO authentication with JBoss SEAM





The new security model in SEAM makes it easy to plug in different authentication mechanisms. After a couple of hours of hacking (OK, it was more like 6 hours...),  I had the sample SEAM booking demo using OpenSSO for authentication. 


The user enters their OpenSSO username and password on the Booking demo log on page, and the application uses the OpenSSO APIs to authenticate to an OpenSSO server. [If you are trying this out at home, remember that the user must be registered in both OpenSSO AND the SEAM application for this to work. If the user authenticates to OpenSSO, but is not registered in the Booking demo, they will get an error message to this effect].



This code has a long way to go to be truly useful. For example, it does not handle any of the following:

  • Single Sign On with OpenSSO. The current example is authentication only. It does not check for an existing SSO token
  • Redirection to the OpenSSO login page
  • SEAM or JEE Role support
  • Session timeouts, enforcement of URL policy, single logout, etc.


Policy Agent vs. Agentless Deployment



The easiest way to obtain the above missing features is to install an OpenSSO policy agent in the hosting container (GlassFish, in my case).   That being said, I would really love to be able to get this working without requiring the installation of a policy agent.

It would be nice to hand someone a .war file, and have it "just work" without any modification of the container.   This may turn out to be a rather difficult exercise. We will see how far I get...


Deploying the Example



If you are interested in playing with the example, you can download the NetBeans project from http://mediacast.sun.com/share/warren/seamBookingWithOpenSSO.zip

You will also need to download and install OpenSSO, and I suggest you also get the OpenSSO examples as well.

The deployed .war file must contain the amsdk.jar file. In addition, you will need to include an AMConfig.properties file that matches your environment. This configuration file is used by the client code to find and authenticate against the OpenSSO server.

The easiest way to generate a valid AMConfig file is to run the "setup" script in the OpenSSO examples, and test it using the sample "login" script.


Most of the action (no pun intended) is in the AuthenticatorAction.java code. Pasted here for your viewing pleasure:


package org.jboss.seam.example.booking;

import com.sun.identity.authentication.AuthContext;
import com.sun.identity.authentication.spi.AuthLoginException;
import java.io.IOException;
import java.util.List;
import static org.jboss.seam.ScopeType.EVENT;
import static org.jboss.seam.ScopeType.SESSION;
import javax.ejb.Remove;
import javax.ejb.Stateful;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.TextOutputCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.jboss.seam.annotations.Destroy;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Out;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.core.FacesMessages;
import org.jboss.seam.log.Log;
import org.jboss.seam.security.Identity;

/\*\*
 \*
 \* @author warren
 \*/
@Stateful
@Scope(EVENT)
@Name("authenticator")
public class AuthenticatorAction implements Authenticator {
    @In Identity identity;
   
    @PersistenceContext EntityManager em;
   
    @Out(required=false, scope = SESSION)
    private User user;
   
    @Logger private Log log;
   
    private static final String orgName = "opensso";
    private static final String moduleName = "DataStore";
   
    /\*\*
     \*
     \* @return
     \* @throws com.sun.identity.authentication.spi.AuthLoginException
     \*/
    protected AuthContext getAuthContext() throws AuthLoginException {
        AuthContext lc = new AuthContext(orgName);
        AuthContext.IndexType indexType = AuthContext.IndexType.MODULE_INSTANCE;
        lc.login(indexType, moduleName);
        return lc;
    }
   
    /\*\*
     \*
     \* @param lc
     \* @return
     \* @throws UnsupportedCallbackException
     \*/
    protected boolean login(AuthContext lc) throws UnsupportedCallbackException {
        boolean succeed = false;
        Callback[] callbacks = null;
       
        // get information requested from module
        while (lc.hasMoreRequirements()) {
            callbacks = lc.getRequirements();
            if (callbacks != null) {
                addLoginCallbackMessage(callbacks);
                lc.submitRequirements(callbacks);
            }
        }
       
        if (lc.getStatus() == AuthContext.Status.SUCCESS) {
            log.info("Login succeeded.");
            succeed = true;
        } else if (lc.getStatus() == AuthContext.Status.FAILED) {
            log.error("Login failed.");
        } else {
            log.error("Unknown status: " + lc.getStatus());
        }

        return succeed;
    }
   
    private void addLoginCallbackMessage(Callback[] callbacks)
            throws UnsupportedCallbackException {
        int i = 0;
        try {
            for (i = 0; i < callbacks.length; i++) {
                if (callbacks[i] instanceof TextOutputCallback) {
                } else if (callbacks[i] instanceof NameCallback) {
                    handleNameCallback((NameCallback)callbacks[i]);
                } else if (callbacks[i] instanceof PasswordCallback) {
                    handlePasswordCallback((PasswordCallback)callbacks[i]);
                } else
                    throw new UnsupportedCallbackException(callbacks[i]);
            }
        } catch (IOException e) {
            log.error("Login Failed", e);
            throw new UnsupportedCallbackException(callbacks[i],e.getMessage());
        }
    }
   
    private void handleNameCallback(NameCallback nc) throws IOException {
        nc.setName(identity.getUsername());
    }
   
    private void handlePasswordCallback(PasswordCallback pc) throws IOException {
        String passwd = identity.getPassword();
        pc.setPassword(passwd.toCharArray());
    }
   
    /\*\*
     \*
     \* @return true if the user was succesfully authenticated, false otherwise
     \*/
    public boolean authenticate() {
        log.trace("Username=" + identity.getUsername() +
                " password=" + identity.getPassword());
        try     {
            User user = null;
            AuthContext lc = getAuthContext();
            log.debug("Got Authcontext=" + lc);
           
            if (login(lc)) {
                log.info("OpenSSO login for user #0 succeeded", identity.getUsername());
               
                if( ! fetchDBUser(identity.getUsername())) {
                    String msg = "OpenSSO login succeded, but there is no user with this name in the datbase";
                    log.error(msg);
                    FacesMessages.instance().add(msg);
                    return false;
                }
               
                return true;
            }
            return false;
        } catch (AuthLoginException ex) {
            log.error("AuthLogin problem", ex);
            return false;
        } catch(UnsupportedCallbackException ex2) {
            log.error("Callback problem", ex2);
            return false;
        }
    }
   
    private boolean fetchDBUser(String username) {
       
        List results = em.createQuery(
                "select u from User u where u.username=:username")
                .setParameter("username", username)
                .getResultList(); 
        if ( results.size()==0 ) {
            return false;
        } else {
            user = (User) results.get(0);
            return true;
        }
    }
   
   
    @Remove @Destroy
    public void destroy() {}
}




powered by performancing firefox

Comments:

SEAM used to be the name for the Solaris Secure NFS with GSS-API and Kerberos V product that used to be unbundled and subsequent ended up being fully bundled in Solaris 10. We don't use the name "SEAM" for this anymore, but even so, JBoss' use of "Seam" is bound to be confusing...

Posted by Nico on March 01, 2007 at 03:00 AM MST #

Post a Comment:
Comments are closed for this entry.
About

Things that amuse me

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today
News

No bookmarks in folder

Blogroll