Tuesday, June 10, 2008

Authentication With Seam

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 have used Seam's authentication framework, you know it is extremely easy to use. However, if you want to add some custom messaging in there, it is NOT as easy to use. We will go over here a work around to that issue. But first let's discuss how to create Seam authentication in general.

The main component to remember is the `Identiy` class. This will store the user, password, and provides the methods on it for logging 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}"/>


As you see everything here references the `Identity`, now thats just the first part. We need to tell Seam what class/method to use for authentication.
In your *components.xml* you will define an authentication method and class to call when `#{identity.login}` is called.

components.xml

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


Then you simply define a Seam component named "authenticator" with a method "authenticate". By default Seam allows you to inject an object called `Identity` ( a Seam Session scoped component). The `Identity` object contains the username, password, and roles associated with the user, the first two will be used for logging in. 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
}
}



Simple enough eh? It's simple and straight to the point. However, there is one **IMPORANT** thing to remember when using Seam Security. And that is Authentication != Login. This distinction will for the most part not effect 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 the username or password is in correct, or if you are using something like LDAP or CAS, 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 is just that its "authentication" it is NOT logging in. 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.

So how do we fix this? Well the answer is, we do 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 object you want to it.

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, but not that bad. 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 `Identity` component.

The last method in the code defines the `Observer`. The `Observer` is called when the event is raised, so we are going to set it to a class level string.

Finally, you will see the login. It again is fairly straight forward as well. 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`.

And that is it. You now will display errors the screen without them displaying multiple times.

While this is not the MOST straight forward way to add messages, it unfortunately is one of the ways you can do it in Seam. I have seen quite a few questions in the past at conferences and message boards about why dual messages are sent back. I hope this help explains it and shows you a way around the issue.

6 comments:

Jeremy Girard said...

Thanks a lot ...
Works fine

Unknown said...

Thank you, solved my problem.

Unknown said...

Very helpful notes.Easy to understand language .thanks u helped me alot.

Unknown said...

Hi. I am using your code for customIdentity, but on startup of JBoss server, I get the following

Unknown said...

Sorry, the error is
no such setter method

Unknown said...

Hi. I got the application to call the customIdentity class. But now, even though I set the message, it still shows "Please log in first". Any ideas please....