Working with Rails

Recommend Brian on Working With Rails


Powered

Authentication with Seam

Posted by Joseph Nusairat Wed, 16 Jul 2008 04:00:00 GMT

One characteristics of modern web frameworks is to provide lots of functionality out of the box. This functionality ranges from authentication, to batch processing, to validation. For those of you who haven't used Seam's authentication framework; rest assured it is extremely easy to use. However, if you want to add some custom messaging things get a little more complex. I will show you a work around for the custom messaging issue but first let's discuss how to deal wih Seam authentication in general.

The main component to remember is the Identity class. This class stores the user, password, and provides the methods for login in and out. This class is provided by Seam out of the box. In the first part we will create the login itself.

The login can be fairly simple and will use the Identity object. Here is a simple login form.

Partial of login.xhtml

<h:form id="login">
<h:inputText id="username" size="20" value="#{identity.username}" required="true"/>
<h:inputSecret id="password" size="22" value="#{identity.password}" required="true"/>
<h:selectBooleanCheckbox id="rememberMe" value="#{identity.rememberMe}"/>
<h:commandButton value="Login" action="#{identity.login}"/>

In your components.xml we need to tell Seam what class/method to use for authentication by defining an authentication method and class to call when #{identity.login} is called.

components.xml

<security:identity 
    authenticate-method="#{authenticator.authenticate}"/>

Then you define a Seam component named "authenticator" that provides an "authenticate" method. By default Seam allows you to inject the Identity object (a Seam Session scoped component). A base skeleton of this code is here:

Authenticator.java

@Name("authenticator")
public class Authenticator {
    @In
    Identity identity;

    public boolean authenticate() {
        // Do your database, ldap, etc lookup
        // then return true/false based on if it was successful
    }
}

It's that simple however, there is one IMPORTANT thing to remember when using Seam Security. And that is Authentication != Login. This distinction will for the most part not affect you unless you want to start dealing with error messages.

By default there are two messages, a failure or successful login that are displayed by Seam automatically when an authentication passes/fails. Both of these are defined in the message.resources file as :

message_en.properties

org.jboss.seam.loginFailed=Login failed!
org.jboss.seam.loginSuccessful=Welcome

You can change the messages there. However, what if your requirements are more complex? What if you want to display a message that specifically informs that the username or password is incorrect, or if you are using something like LDAP, and you want to report that their servers are down. Simple enough right? Just do a FacesMessage context look up and add to it? Right? Wrong! What's the problem with this? You run a good chance that the error message will show up twice on the screen. While this isn't the worse thing to happen, it is a bit annoying. So why does this happen? The authenticator provides just that; "authentication" and is not actually performing the login logic. Meaning that the authenticator is actually called multiple times depending on the scenario and you do not necessarily have control over when and how often the authenticator is called.

To fix that we start by not write messages directly inside the authenticator. Instead, we will write login messages where they should be written, at login. So how do we do this while still using Seam's login? One way is to use a combination of the Seam Events/Observers and to write a "custom login".

The purpose of the Events/Observer, is actually quite interesting. You can write an event in one area of the code, and the Observer can pick it up based on a keyword in a separate class and process it. So in the authenticator you can add this line of code whenever you want to raise a specific message.

Events.instance().raiseEvent("invalidLogin", "Invalid password!!");

The first part of the method contains the key "invalidLogin", the second part displays whatever string we want to display to the front end. Please note, the actual method takes an array of Objects for it's second parameter, so you could pass as many strings, or whatever objects you want.

We have now saved this event, now comes the part of retrieving it and storing it to the FacesMessages, this part is a BIT trickier. We need to create our own Identity class. The Identity, as I explained is the component that actually performs the logging in. But wait just earlier i said that Identity is a Seam level component. What we are going to do is basically extend the Identity class and then replace the component with ours. Lets take a look at our custom identity component.

CustomIdentity.java

@Name("org.jboss.seam.security.identity")
@Scope(ScopeType.SESSION)
@Install(precedence = Install.APPLICATION)
@BypassInterceptors
@Startup
public class CustomIdentity extends Identity {
    private static final long serialVersionUID = -9154737979944336061L;

    @Override
    public String login() { 
        String retVal = "";
    try {
            // clear out the login message in case it was triggered
            // by an authenticate occurring outside the login
            loginErrorMessage = null;           
            retVal = super.login();         
        } finally {         
            // check if any error messages were registered from
            // this logging., if they are write them out            
            if (StringUtils.isNotBlank(loginErrorMessage)) {
                FacesMessages.instance().add(loginErrorMessage);
            }
        }       
        return retVal;
    }

    private String loginErrorMessage;

    /**
     * This is used to save off an error message in case of a login.
     * @param message
     */
    @Observer("invalidLogin")
    public void writeInvalidLogin(String message) {         
          loginErrorMessage = message;      
    }
}

This code is a bit of a mouthful, but really not that hard to implement. The class level annotations are similar to the ones by default with Identity except we change the precedence to a later time. By default Identity has a BUILT_IN precedence. By changing it to APPLICATION we guarantee that our custom component will be the one used by Seam instead of Seam's own Identity component.

The last method in the code defines the Observer which is called when the event is raised, we are going to set it to a class level string. Finally in the login method we are going to call the parent's login method (which is Seam's Identity login method) but after the login call we will check to see if any messages were saved by our Events/Observer pattern and if they are we will add them to the FacesMessages.

With those changes in place you will now be able to display login errors without showing them displaying multiple times on a page.

Posted in , , ,  | no comments | no trackbacks

Comments

Trackbacks

Use the following link to trackback from your own site:
http://www.integrallis.com/blog/trackbacks?article_id=authentication-with-seam&day=16&month=07&year=2008

Comments are disabled

Categories

Archives

Syndicate