Tuesday, May 26, 2009

Minor updates to the Seam-based user registration example ...

Based on feedback from the community, I made the following updates to the Seam example (nothing too major except verifying that the solution works with the latest RichFaces release)

  • Tested with RichFaces 3.3.1 GA -- works great!
  • Updated the GuestSupport class to use Seam's StatusMessages instead of working directly with FacesMessage(s).
  • Updated the registration form to use Seam's <s:decorate> tag and a Facelets template, which really simplified the form.
  • Moved the solution-specific text messages from i18n.properties into the Seam default messages.properties ... got tired of having two separate message bundles at work ...
  • Cleaned up a few JPA annotations to be more inline with the spec.
  • Localized the last few Hibernate Validator error messages that had hard-coded English text in them.
  • Created the Gender and ProfilePolicy enums used by the Preferences entity.

The updated source code is available here.

NOTE: If you want to upgrade to RichFaces 3.3.1, then you need to remove the version information from the JAR filenames:

richfaces-api-3.3.1.GA.jar rename to richfaces-api.jar
richfaces-impl-3.3.1.GA.jar rename to richfaces-impl.jar
richfaces-ui-3.3.1.GA.jar rename to richfaces-ui.jar

Wednesday, May 13, 2009

User registration solution using JBoss Seam with JSF / RichFaces and Hibernate

In this article, I show you how to implement a Web 2.0 style user registration solution using JBoss Seam with JSF/Facelets/RichFaces and Hibernate. This article evolved from a real Web 2.0 site I'm building. You can learn more about me on my LinkedIn profile. As for what's in it for you, I've provided the source code and a working solution using:

In future articles, I plan to incorporate Hibernate Search and some collective intelligence techniques such as tag clouds, clustering, collaborative filtering, and recommendations. While writing this article, I assumed the reader would have a basic understanding of JSF and Hibernate. However, I do spend extra time explaining some of the key features of Seam and RichFaces.

I'd like to give credit to Dan Allen as I've used many of the ideas he teaches in Seam In Action in my own Seam development; his book is one that all Seam developers should own!

Basic Requirements

Here are the high-level requirements this solution satisfies. Notice that most these requirements apply to many Web 2.0 sites seen on the Web today.

1. Public-facing Web site with read-only access to Guests
The site should be useful without logging in and the content should be accessible to search engines. Since casual surfers will be visiting the site, it must load fast and be easy to use on all major browsers (IE 6 / 7, FireFox 3.x, and Safari 3.x).

Site

   Jump to solution ...

2. Open registration process with email verification
Guests should be able sign-up for a new account using a simple form that collects minimal information from the user; CAPTCHAs should be used to prevent bots from creating accounts. Each user must agree to the site's terms of use before registering. After registering, the user will also need to click on an activation link sent to their email, which ensures they have access to the email account provided on the form.

Registration Form

   Jump to solution ...

3. Remember Me
Registered users can request the site to remember their credentials for automatic login for future visits.

Sign-In Form

   Jump to solution ...

4. Ability to recover forgotten passwords
Since we have a valid email for every user, users can request a new temporary password to be emailed to them. Upon logging in with the temporary password, the user should be prompted to change their password to a permanent value.

Recover Password Form Password Reset Confirmation Dialog Change Password Dialog

   Jump to solution ...

5. User Preferences
Registered users can share personal information, upload a photo, and setup site preferences, such as whether they are interested in receiving emails about special promotions from the site.

User Preferences

   Jump to solution ...

6. Strong Server-side Validation
Since we're exposing the site to the Wild Wild Web, server-side validation is essential; client-side validation using JavaScript is a nice to have, but is not sufficient. It's trivial for a hacker to by-pass client-side validation so you want to make sure your server code is protected as well. Where appropriate, use AJAX to give immediate feedback to the user when they've entered invalid data.

AJAX Validation

   Jump to solution ...

Seam-based Architecture

In a previous blog posting, I used Spring with JSF / RichFaces to satisfy these requirements and advocated a layered architecture. While that is still a valid approach, I take a different approach in this article to highlight key features of Seam. Specifically, instead of covering each layer, I describe how I implemented each requirement. The layers are there if you need them, but with Seam you're not forced to think in terms of stateless layers. What initially attracted me to Seam was its get down to business approach. First off, it comes with a tool seam-gen which creates the skeleton project for your application, including generating JPA-based Entity code by reverse engineering a database schema. With Seam, you can get right to work building your UI that directly interacts with your entities. In contrast, with my Spring-based solution, I had to flush out the persistence and business-service layers consisting of the following interfaces and classes:

example.user.UserService (interface)
example.user.impl.UserServiceImpl (class)
example.user.dao.UserDao (interface)
example.user.dao.RoleDao (interface)
example.user.dao.impl.HibernateUserDao (class)
example.user.dao.impl.HibernateRoleDao (class)

Seam, on the other hand, treats the persistence manager as the DAO and provides a framework for CRUD components (see Seam Application Framework). I'll address the Seam Application Framework in a future article, for now I'm going to stay focused on the requirements at hand. I just wanted to make sure you knew that I wasn't throwing the persistence and business-service layers out the window by using Seam. They are there when you need them but are not in the way otherwise.

Here are some of the key aspects of Seam that I'll be using to satisfy the requirements:

  • POJOs with bi-directional dependency injection known as bijection (note: I'm not using EJB3 for this project, but Seam makes it really easy to use EJBs if you need them)
  • Object-Relational Mapping (ORM) using the Java Persistence API (JPA) and Hibernate under the covers.
  • Declarative transaction management using Seam annotations
  • JSF with Facelets and RichFaces
  • Test-driven development based on TestNG
Seam Project Organization

Here is the link to the Seam project created by seam-gen. Before digging too deeply into the details of the configuration, take moment to familiarize yourself with the organization of the Seam project:

Seam Project Structure

NOTE: You'll need to copy the lib directory from your Seam 2.1.1.GA directory to my project directory as I excluded the lib directory to minimize the size of the download.

Object Persistence via JPA
Domain Entities

There are three primary domain entities: User, Preferences, and Role, which should be self-explanatory given the requirements outlined above. The entities are POJOs with the exception of using Seam, JPA, and Hibernate annotations in a few key places. For example, in the following code snippet, we annotate the example.user.User class as a JPA Entity and map it to the ex_user table in the database:

@Entity
@Table(name = "ex_user", uniqueConstraints = @UniqueConstraint(columnNames = "user_name"))
public class User implements Serializable ...

Notice that I'm using surrogate keys for my domain object identifiers, which are auto-generated by Hibernate using the best approach for the specific database-type:

@Id @GeneratedValue(generator="native")
@GenericGenerator(name="native", strategy = "native")
@Column(name = "user_id", unique = true, nullable = false)
public Long getUserId() {
    return userId;
}

I'm also using the Hibernate Validator annotations to restrict the values for some of the members, such as the user's email address:

@Column(name = "email", nullable = false, length = 60)
@NotNull
@Length(max = 60)
@Email
public String getEmail() {
    return this.email;
}
Domain Object Associations

There is a one-to-one relationship between the Preferences and User objects. The Preferences object is loaded with the User object (fetch=FetchType.EAGER) because it contains information that is needed to render the UI for the user.

@OneToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
@PrimaryKeyJoinColumn
public Preferences getPreferences() {
    return this.preferences;
}

There is a bidirectional many-to-many relationship between the User and Role objects using a join table ex_user_role.

@UserRoles
@ManyToMany(targetEntity = Role.class,cascade = {CascadeType.PERSIST,CascadeType.MERGE})
@JoinTable(name = "ex_user_role", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id"))
public Set getUserRoles() {
    if (userRoles == null) {
        userRoles = new HashSet ();
    }
    return userRoles;
}

On the Role object, we have:

@ManyToMany(cascade={CascadeType.PERSIST,CascadeType.MERGE},mappedBy="userRoles",targetEntity=User.class)
public Set getRoleUsers() {
    if (roleUsers == null) {
        roleUsers=new HashSet ();
    }
    return roleUsers;
}

This solution comes directly from the Hibernate documentation, section 7.5.3.

Notice that my entity classes are in the src/main directory as these classes cannot be incrementally hot-deployed by Seam. This means that if you change the code in these classes, then you need to recycle the Web application before the change is activated. On the other hand, classes in the src/hot directory are updated by Seam on-the-fly without recycling the Web application. Consequently, you want to put as many of your classes in the src/hot directory as possible (see: 2.8. Seam and incremental hot deployment).

Now that you understand the key domain entities involved in this solution, you're ready to see how I implemented each requirement.

Solution for Requirement #1 (Public-facing Web site with read-only access to Guests)

Much like many Web 2.0 sites today, my site will be useful to casual guests of the site. So it needs to load quickly and look attractive on all major browsers (IE 6-7, FireFox 3.x, and Safari 3.x). Thankfully, JSF and RichFaces satisfy these requirements out-of-the-box. Here are some of the reasons why I like JSF with RichFaces:

  • Modern, professional looking skin out-of-the-box (I don't like to fool with CSS).
  • Extensive set of easy-to-use, yet flexible UI components, such as trees, data grids, tabbed panes, etc.
  • Easy to create reusable custom components.
  • Large community of users and developers building real apps.
  • Performance: loads and runs fast on all modern browsers.
  • Minimizes hand-coded JavaScript.
  • Templates using Facelets

Key design decision: I decided to use the RichFaces ModalPanel component for my forms because it helps the user keep their current orientation with the site. Instead of navigating to a different page, I simply open a dialog over the current view. Consequently, I didn't have any need for JSF navigation rules in this example because there is only one page.

Solution for Requirement #2 (Open registration process with email verification)

For this requirement, it's useful to break it down into several detailed steps:

  1. Guests are presented with a link to register.
  2. Simple form to collect minimal user information.
  3. Require the user to solve a CAPTCHA to prevent bots from creating accounts.
  4. Require the user to agree to the site's terms of use.
  5. Validate the user's email address before activating the account.
  6. Activate the account when the user clicks on a link in the activation email.
Solution for Requirement #2a (Register Link)

In the view/WEB-INF/facelets/headerControls.xhtml file, I open the registerPanel dialog using a rich:componentControl which calls the show method of the rich:modalPanel component when the link is clicked:

<h:outputLink value="#" id="registerLink">
  <h:outputText value="#{i18n.register}"/>
  <rich:componentControl for="registerPanel" attachTo="registerLink" operation="show" event="onclick"/>
</h:outputLink>
Solution for Requirement #2b (Simple form to collect minimal user information)

In the view/WEB-INF/facelets/guestSupport.xhtml file, I use a rich:modalPanel to hold the registration form. My registration form only requires the user to supply a screen name, password, email, first, and last name. Notice that the form fields are bound to properties of a guest component, for example:

<h:inputText label="#{i18n.login_username}" id="userName" required="true" 
value="#{guest.registrationScreenName}" size="12" maxlength="12" redisplay="true">
  ...
</h:inputText>

The guest object is a Seam component example.action.GuestSupport that handles the registration process, see: src/hot/example/action/GuestSupport.java. Notice the annotations on the GuestSupport class:

@Name("guest")
@Scope(ScopeType.EVENT)
public class GuestSupport ...

The @Name annotation means that this is a Seam component that can be referenced in the EL using the identifier guest, such as: #{guest.registrationScreenName}. The @Scope(ScopeType.EVENT) annotation means that the component is managed in Seam's EVENT scope, which means the component exists from the Restore View phase until the end of the Render Response phase in the JSF lifecyle. Essentially, the guest component exists only to handle a guest action and then is released by the Seam container. If no guest actions are performed, then this component is never created.

The form is bound to the guest.doRegister method:

<a:commandButton type="button" id="registerButton" value="#{i18n.register}" action="#{guest.doRegister}"/>

Notice that I'm using a RichFaces AJAX form <a:form> which means the register action will be submitted as an AJAX request without reloading the entire page.

Solution for Requirement #2c (CAPTCHA)

In my Spring solution, I integrated with reCAPTCHA. However, Seam comes with a CAPTCHA solution built-in so I decided to give it a try. Here is how to embed the Seam CAPTCHA solution into your form:

<s:decorate id="verifyCaptchaField" template="captcha.xhtml">
  <h:graphicImage id="captchaChallenge" value="/seam/resource/captcha" styleClass="captchaChallenge"/>
  <h:inputText id="verifyCaptcha" value="#{captcha.response}" required="true" size="3"/>
</s:decorate>

The default solution is to show an image of a simple addition problem, which unlike reCAPTCHA is much easier to read and is probably pretty safe unless your site is for preschoolers who might not be able to solve the addition problem.

Seam CAPTCHA on Registration Form

From what I've seen, it's also pretty easy to extend the built-in solution to provide your own image creation solution. You should note that if you use Seam's CAPTCHA solution, then you must make sure the Seam Resource Servlet is activated in your web.xml file (done automatically by seam-gen).

Solution for Requirement #2d (Terms of Use)

I built a placeholder HTML file to hold the site's legal text, see: view/WEB-INF/facelets/legal.html. If the user submits the form without agreeing to the terms of use, then a nice error message is returned. This is handled by the following code in my doRegister method:

if (!agreedToTermsOfUse) {
    String msg = facesSupport.getMessage("please_agree_to_terms", extCtxt.getRequestLocale());
    jsf.addMessage("registerForm:registerButton", new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, null));
    return;
}
Solution for Requirement #2e (Email Verification)

The doRegister method creates the user but does not activate the account. Instead, it creates an activation key which is an MD5 hash of the user's information and the current timestamp. The timestamp is needed because there is a very slight chance of getting the same hash code for two different users. The activation key is sent to the user as a link back to the site that will activate their account.

Seam uses Facelet templates as email templates. For account verification, I created the view/WEB-INF/facelets/email/activation.xhtml template. Notice that I can use the EL to reference my Seam components just as I would for an HTML template:

<m:from name="JSF / RF Seam Example" address="support@saastk.com"/>
<m:to name="#{inactiveNewUser.email}">#{inactiveNewUser.email}</m:to>
<m:subject>JSF / RF Seam Example Account Activation</m:subject>
<m:body>
  <html>
    <body>
      <p>Dear #{inactiveNewUser.displayName},</p>
      <p>Please click on the following link to activate your account:</p>
      <p><a href="#{inactiveNewUser.activationLink}">#{inactiveNewUser.activationLink}</a></p>
    </body>
  </html>
</m:body>

The registrationMailer component processes the email templates and sends the email, see: src/hot/example/action/RegistrationMailer.java. Notice the annotations on the RegistrationMailer class:

@Name("registrationMailer")
@AutoCreate
@Scope(ScopeType.APPLICATION)
public class RegistrationMailer ...

The @Name annotation means that this is a Seam component that can be referenced in the EL using the identifier registrationMailer. The @Scope(ScopeType.APPLICATION) annotation means that the component is managed in Seam's APPLICATION scope, which means the component exists while the Web application is active. Essentially, there will be only one registrationMailer used by the Seam container. @AutoCreate means that Seam will create this component for us automatically.

I decided to send the email as part of the transaction meaning that if the email fails, then no account is created. The following code puts an instance of the aptly named InactiveNewUser bean into EVENT scope and the raises a synchronous application event.

Contexts.getEventContext().set("inactiveNewUser",
  new InactiveNewUser(newUser.toString(), newUser.getUserName(), newUser.getEmail(), newUserLink));
Events.instance().raiseEvent("userRegistered");

The RegistrationMailer is an observer of this event:

@Observer("userRegistered")
public void sendActivationEmail() {
    renderer.render("/WEB-INF/facelets/email/activation.xhtml");
}

Supposedly, you can send the email asynchronously, but I ran into trouble so I stayed with the synchronous approach for now. I'll post to the Seam forum to see what I can find out ...

NOTE: Be sure to configure a <mail:mail-session host="???" port="25"/> in the Seam configuration file: resources/WEB-INF/components.xml. See Chapter 21. Email

Solution for Requirement #2f (Account Activation)

Assuming all goes well and the email provided by the user is valid, then they will receive an email with a link back to the site, such as:

http://localhost:8989/jsf_rf_seam_user_reg/home.seam?act=19eb2e7567913c47a931e305cdeb6d311242247928343

When the user clicks on the link, we need to process the act request parameter. Fortunately, Seam makes this really easy to do using a page action. In resources/WEB-INF/pages.xml I declare an action for the home.xhtml page:

<action execute="#{guest.doActivate(request.getParameter('act'))}" 
  if="#{request.getParameter('act') != null}"/>

In plain English, Seam invokes the guest.doActivate method if the request contains the act parameter. If activation is successful, then I trigger the Login form to open up with the newly activated username pre-populated.

@Transactional
public void doActivate(String activationKey) {
    Query q = entityManager.createQuery("from User u where u.active=0 AND u.activationKey=:activationKey");
    q.setParameter("activationKey", activationKey);
    User activatedUser = (User)q.getSingleResult();
    activatedUser.setActive(true);
    activatedUser.setActivationKey(null);
    loginUserId = activatedUser.getUserName();
    credentials.setUsername(loginUserId);
    log.info("User {0} activated successfully.", loginUserId);
}

NOTE: You may want to have a background process that cleans up accounts that haven't been activated after so many days.

Solution for Requirement #3 (Remember Me)
Login

Before I can tackle the Remember Me requirement, I need to make sure you know how login works with Seam. If you search my code, you'll see that there is no login method. Instead, I rely on Seam's Identity Management framework to do the login work for me. The Identity Management framework requires an identity store in resources/WEB-INF/components.xml:

<security:jpa-identity-store user-class="example.user.User" role-class="example.user.Role"/>

Notice that I'm reusing my User and Role Entities that I've already developed. My login form is bound to Seam's identity.login method:

<h:commandButton type="submit" id="loginBtn" action="#{identity.login}" value="#{i18n.login}"/>

See loginPanel in view/WEB-INF/facelets/guestSupport.xhtml

If login is successful, then Seam raises the Identity.EVENT_LOGIN_SUCCESSFUL event; if login fails, then Seam raises the Identity.EVENT_LOGIN_FAILED event. Thus, my GuestSupport component is configured as an @Observer for these events:

@Observer(Identity.EVENT_LOGIN_SUCCESSFUL)
public void onLogin() {
    this.currentUser = byUserName(credentials.getUsername());
    log.info("User {0} logged in successfully.", this.currentUser.getUserName());
}

@Observer(Identity.EVENT_LOGIN_FAILED)
public void onLoginFailed() {
    this.currentUser = null;
    FacesContext jsf = FacesContext.getCurrentInstance();
    String msg = facesSupport.getMessage("login_error", jsf.getExternalContext().getRequestLocale());
    jsf.addMessage("loginForm:loginBtn", new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, null));
}

In the onLogin method, we see an application of Seam's outjection @Out in action. If login succeeds, then we outject the currentUser object into Seam's SESSION Scope as seen by:

@Out(required=false, scope=ScopeType.SESSION)
private User currentUser;

So, unlike Spring, Seam can easily export runtime objects into the container using simple annotations! Once outjected, the currentUser component can be used seamlessly in our JSF UI.

At this point, you may be wondering how Seam verifies the credentials supplied by the user against the database. There is a little annotation magic going on here that is not immediately apparent. If you study the example.user.User class, you'll see that I'm using a few annotations from the org.jboss.seam.annotations.security.management package:

@Column(name = "user_name",unique = true,nullable = false,length = 16)
@NotNull
@Length(min = 4,max = 16)
@Pattern(regex = "^[a-zA-Z\\d_]{4,12}$",message = "Invalid screen name.")
@UserPrincipal
public String getUserName() {
    return this.userName;
}

...

@Column(name = "password",nullable = false,length = 128)
@NotNull
@Length(max = 128)
@UserPassword(hash = "SHA")
public String getPasswordHash() {
    return this.passwordHash;
}

The @UserPrincipal and @UserPassword annotations are used by the Seam Identity Management framework to verify the credentials during login. With these annotations, Seam has all the information it needs to validate credentials against a one-way SHA-1 hash stored in the database.

Remember Me

Seam supports two approaches to remembering users when they return to your site, aptly named Remember Me: 1) only the username is remembered, or 2) a persistent access token is stored in a cookie to allow full automatic login. If you read the Seam documentation, you are strongly advised against the latter approach due to cross-site scripting vulnerabilities. While I understand those concerns, it is still a really nice feature to offer for users. Consequently, I'm going to show you how I implemented the persistent token approach and if you don't decide to use it, then it is very easy to rollback to the username only solution. I actually think the username-only solution is pretty slick if implemented correctly. LinkedIn is a good example of the username only solution where it recognizes you and shows your profile in read-only mode. LinkedIn only requires you to login if you try to make a change to your profile.

There are four steps you need to do to enable automatic login:

First, you need to activate Seam's rememberMe component in the resources/WEB-INF/components.xml file and set the mode to autoLogin:

<security:jpa-token-store token-class="example.security.AutoLoginToken"/>
  <security:remember-me mode="autoLogin"/>

The rememberMe component needs a token store. Seam provides a JPA-based implementation org.jboss.seam.security.JpaTokenStore that stores the token in your database, but you need to provide the Entity class, see src/main/example/security/AutoLoginToken.java. The AutoLoginToken implementation came directly from 15.3.5.1. Token-based Remember-me Authentication in the Seam documentation.

Second, you need to add a checkbox on the login form bound to the rememberMe component's enabled property:

<h:selectBooleanCheckbox id="rememberMe" value="#{rememberMe.enabled}"/>

Third, you need to invoke the identity.tryLogin method when the home page is requested. As with the activation link processing, I configured a simple page action for home.xhtml in pages.xml:

<action execute="#{identity.tryLogin}" if="#{not identity.loggedIn}"/>

The Seam documentation mentioned using the following event declarations in components.xml, but that did not work for me:

<event type="org.jboss.seam.security.notLoggedIn">
  <action execute="#{redirect.captureCurrentView}"/>
  <action execute="#{identity.tryLogin()}"/>
</event>
<event type="org.jboss.seam.security.loginSuccessful">
  <action execute="#{redirect.returnToCapturedView}"/>
</event>

Lastly, you need to observe the Identity.EVENT_POST_AUTHENTICATE event and out-ject the currentUser component into Session scope if auto-login was successful:

@Observer(Identity.EVENT_POST_AUTHENTICATE)
public void postAuthenticate(Identity identity) {
    if (identity != null && identity.isLoggedIn() && this.currentUser == null) {
        String userName = identity.getUsername();
        if (userName == null) userName = identity.getPrincipal().getName();
        if (userName != null) {
            this.currentUser = byUserName(userName);
            log.info("User {0} logged in successfully.", this.currentUser.getUserName());
        }
    }
}

The Identity.EVENT_LOGIN_SUCCESSFUL event was not raised by the rememberMe component upon successful auto-login, which may be a bug? I'll ask the Seam folks ... for now, you have to have this additional @Observer in place :-(

Solution for Requirement #4 (Ability to recover forgotten passwords)

If a user forgets their password, then they can request the site to email a temporary password by submitting their screen name and email.

Recover Password Form

Since this is an action performed by a guest of the site, the form is processed by the guest component:

@Transactional
public void doRecoverLostPassword() throws Exception {
    ...
}

The @Transactional annotation tells Seam that this method must be invoked within the context of a transaction. If the method completes successfully, then Seam will commit the work for you regardless of the underlying transaction manager your application is using.

When the user logs into the site with their temporary password (sent in an email), the site needs to prompt them to change the password to a permanent value. As before, to keep the user's orientation with the site, I pop-up a modal dialog to require the user to change their password, see chngPswdPanel in view/WEB-INF/facelets/userPreferences.xhtml.

Change Password Dialog

The modal panel will only show itself if the user's password is temporary:

<rich:modalPanel id="chngPswdPanel" width="320" height="240" rendered="#{identity.loggedIn}" showWhenRendered="#{currentUser.temporaryPassword}">

The change password form is handled by the changePassword component, see: src/hot/example/action/ChangePasswordAction.java

Solution for Requirement #5 (User Preferences)

Once logged in, the user can click on the Preferences link to edit their profile and control how they want the site to interact with them. The prefPanel dialog in userPreferences.xhtml is activated using the following AJAX Link a:commandLink in headerControls.xhtml:

<a:commandLink id="prefLink" action="#{updatePreferences.beginEditPreferences(currentUser.userId)}" oncomplete="#{rich:component('prefPanel')}.show()" value="#{i18n.preferences}"/>

Look closely at the EL for the action attribute; I'm using a new component updatePreferences, see: src/hot/example/action/UpdatePreferencesAction.java.

@Name("updatePreferences")
@Scope(ScopeType.CONVERSATION)
@Restrict("#{identity.loggedIn}")
@Transactional
public class UpdatePreferencesAction implements java.io.Serializable ...

The Scope of this component is ScopeType.CONVERSATION, which means this component will exist for the duration of a conversation, possibly spanning multiple requests from the user. The Seam documentation equates a conversation with a single unit-of-work from the perspective of the user (not the database). In this case, the unit-of-work is to edit preferences, which may include uploading a photo as well as submitting an AJAX form one or more times. So the conversation begins when the user opens the preferences dialog and ends when they close it. Here is the beginEditPreferences method:

@Begin(flushMode = FlushModeType.MANUAL, join = true)
public void beginEditPreferences(Long userId) {
    currentUser = entityManager.find(User.class, userId); // attach to this EntityManager
    log.info("User {0} has started editing preferences.", currentUser.getUserName());
}

When the conversation begins, I use the entityManager to get the User Entity for the current user, identified by the userId parameter. Of course, there is a currentUser component in Session Scope (out-jected during the login process). However, it is not attached to the updatePreferences component's entityManager (it is a detached Entity that is only useful for reading data about the current user). When the beginEditPreferences method completes, the currentUser stays attached to the entityManager for the duration of the conversation.

While the preferences dialog is open, the user can submit several requests to the server, such as uploading an image. For image upload, I employ the RichFaces rich:fileUpload component:

<rich:fileUpload fileUploadListener="#{updatePreferences.fileUploadListener}" maxFilesQuantity="1" id="uploadPhoto" immediateUpload="true" acceptedTypes="jpg,gif,png" allowFlash="false" listHeight="110">
  <a:support event="onuploadcomplete" reRender="prefPanel,uploadPhotoPanel"/>
</rich:fileUpload>

The fileUploadListener method participates in the conversation accessing the currentUser that was attached to the entityManager when the conversation began.

When the user closes the preferences dialog, the conversation ends by invoking an AJAX call to updatePreferences.endEditPreferences which is annotated with @End:

@End
public void endEditPreferences() {
    entityManager.flush(); // flush the changes to the database
    log.info("User {0} finished editing preferences.", currentUser.getUserName());
}

Notice that the updatePreferences component out-jects the currentUser component back into Session scope (as the guest component did after a successful login). This is needed so that changes are propagated back to the UI, such as the user's display name shown in the top right corner of the application.

Conversation

It may be hard to appreciate Seam conversations with such simplistic objects at play. However, as your object associations become more complex, conversations are immensely powerful in helping you manage updates to your entities.

Solution for Requirement #6 (Strong Server-side Validation using AJAX)

With Seam, you don't need any JavaScript validation--all validation can be done efficiently on the server. For example, notice how I ensure the user's password is strong:

<h:inputSecret label="#{i18n.login_password}" id="password" required="true" value="#{passwordSupport.password}" size="12" maxlength="12">
  <f:validateLength minimum="8" maximum="12"/>
  <rich:beanValidator/>
</h:inputSecret>

On the server, the password is validated against a regular expression using Hibernate Validator:

@NotEmpty
@Length(min=8,max=12)
@Pattern(regex="(?=^.{8,}$)((?=.*\\d)|(?=.*\\W+))(?![.\\n])(?=.*[A-Z])(?=.*[a-z]).*$", message="Password is not secure enough!")
private String password = null;

See: src/hot/example/action/PasswordSupport.java

Most of the forms used in this solution are submitted using AJAX requests. The login form and the image upload form are the only two forms that redisplay the entire page when submitted. Here are some other areas where I've used the AJAX features of RichFaces:

Country / Region

One of the nice things about RichFaces is that you can attach AJAX-driven logic to any JSF component without writing any JavaScript. For example, suppose we want to limit the list of regions based on the selected country on the Preferences form. This is trivial using the <a4j:support> tag provided by RichFaces:

<a4j:support event="onchange" rerender="region" ajaxsingle="true">

This fires an AJAX request when the country is changed and upon success, the region component is re-rendered with a country-specific list of regions.

User name check with AJAX

During registration, if the user's desired screen name is already taken by another user, we should let them know immediately. To do this, we use the <a4j:support> tag provided by RichFaces on the userName <h:inputText> field:

<h:inputText label="#{i18n.login_username}" id="userName" required="true" value="#{guest.registrationScreenName}" size="12" maxlength="12" redisplay="true">
  <f:validateLength minimum="4" maximum="12"/>
  <rich:beanValidator summary="#{i18n.invalid_screen_name}"/>
  <a:support event="onblur" reRender="userName" ajaxSingle="true"/>
</h:inputText>

When the onblur event occurs, RichFaces sends an AJAX request to update the guest.registrationScreenName property. If the desired screen name is already taken, a FacesMessage is created to be displayed by the <rich:message> tag associated with the username field.

<rich:message for="userName">
  <f:facet name="errorMarker"><h:graphicimage value="img/error.gif"></f:facet>
</rich:message>
Test Driven Development with Seam

One of the major attractions of POJO development and frameworks like Spring is the ease in which you can test your code as you develop it outside the container. Seam offers a similar solution, but does require the JBoss micro-container to execute tests. Other than the tests running much slower than similar Spring-based tests, I found testing with Seam to be very easy and effective. On the other hand, I wasn't able to find a simple JSF testing solution for Spring anyway, so just having the SeamTest framework in place is a benefit over Spring, even if it is slower. Seam tests are executed by TestNG. I've implemented a test that performs all the actions needed to support the requirements described above, see: src/test/example/test/UserRegistrationTest.java. Notice that you use the same EL as your JSF Facelets do!

Lastly, I want to mention one thing you should to do to fix an issue with incremental hot deploy. The seam explode command is supposed to hot-deploy your Facelets and Java classes from the hot directory. However, it also updates the JCA ConnectionFactory in the JBoss deploy directory. This can cause issues (see: https://jira.jboss.org/jira/browse/JBSEAM-3844?focusedCommentId=12442804). My solution was to add a new target to my project's build.xml file hotdeploy, which is the same as the explode target except datasource is no longer a dependency:

<target name="hotdeploy" depends="stage" description="Deploy the exploded archive but not the datasource">
  <fail unless="jboss.home">
    jboss.home not set
  </fail>
  <mkdir dir="${war.deploy.dir}"/>
  <copy todir="${war.deploy.dir}">
    <fileset dir="${war.dir}"/>
  </copy>
</target>

You also have to update the build.xml file in the seam-gen directory of your Seam distribution:

<target name="hotdeploy" depends="validate-project" description="Deploy the project as an exploded directory">
  <echo message="Deploying project '${project.name}' to JBoss AS as an exploded directory"/>
  <ant antfile="${project.home}/build.xml" target="hotdeploy" inheritall="false"/>
</target>

IMPORTANT: I excluded the lib directory from the download zip to reduce the file size. You simply need to copy the lib directory from your seam installation to my project folder to get the build working.

Thursday, April 16, 2009

User authentication / registration with JSF / RichFaces, Spring, and Hibernate

In this article, I show you how to implement a Web 2.0-style user registration solution using JSF/RichFaces, Spring, and Hibernate. This article evolved from a real Web 2.0 site I'm building; I hope to document more lessons learned as I have time. You can learn more about me on my LinkedIn profile. As for what's in it for you, I've provided the source code and a working solution using:

In future articles, I also plan to incorporate Hibernate Search and implementations of collective intelligence techniques such as tag clouds, clustering, collaborative filtering, and recommendations.

While writing this article, I assumed the reader would have a basic understanding of the technologies listed above and focused more on how to integrate them to solve the requirements discussed in the next section.

Basic Requirements

Here are the high-level requirements this solution satisfies. Notice that most these requirements apply to many Web 2.0 sites seen on the Web today.

1. Public-facing Web site with read-only access to Guests
The site should be useful without logging in and the content should be accessible to search engines. Since casual surfers will be visiting the site, it must load fast and be easy to use on all major browsers (IE 6 / 7, FireFox 3.x, and Safari 3.x).

Site

2. Open registration process with email verification
Guests should be able sign-up for a new account using a simple form that collects minimal information from the user; CAPTCHAs should be used to prevent bots from creating accounts. Each user must agree to the site's terms of use before registering. After registering, the user will also need to click on an activation link sent to their email, which ensures they have access to the email account provided on the form.

Registration Form

3. Remember Me
Registered users can request the site to remember their credentials for automatic login for future visits.

Sign-In Form

4. Ability to recover forgotten passwords
Since we have a valid email for every user, users can request a new temporary password to be emailed to them. Upon logging in with the temporary password, the user should be prompted to change their password to a permanent value.

Recover Password Form Password Reset Confirmation Dialog Change Password Dialog

5. User Preferences
Registered users can share personal information, upload a photo, and setup site preferences, such as whether they are interested in receiving emails about special promotions from the site.

User Preferences

6. Strong Server-side Validation
Since we're exposing the site to the Wild Wild Web, server-side validation is essential; client-side validation using JavaScript is a nice to have, but is not sufficient. It's trivial for a hacker to by-pass client-side validation so you want to make sure your server code is protected as well. Where appropriate, use AJAX to give immediate feedback to the user when they've entered invalid data.

AJAX Validation

Layered Architecture

To satisfy these requirements, I've implemented a layered architecture depicted in the diagram below:

Layered Architecture Diagram

For simple authentication, Spring Security may seem like overkill. However, as the application evolves, I'm also going to need authorization. In addition, Spring Security supports OpenID, which is also of interest for this site.

Web Application Organization

Here is the link to the source code and WAR file. A simple Ant build script is provided so you can build the source. Before digging too deeply into the details of the configuration, take moment to familiarize yourself with the organization of the Web application:

Web Application Directory Structure

Persistence Layer
Domain Objects

There are three primary domain objects: User, Preferences, and Role, which should be self-explanatory given the requirements outlined above. The domain objects are POJOs with the exception of also using Hibernate annotations in a few key places. For example, in the following code snippet, we annotate the User class as a Hibernate Entity and map it to the ex_user table in the database:

@Entity
@Table(name="ex_user")
public class User implements Serializable ...

Notice that we are using surrogate keys for our domain objects that are auto-generated by Hibernate using the best approach for the specific database-type:

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public Long getUserId() {
    return userId;
}

We also define the userName property to be the natural ID for the User entity. While rare, you can allow the userName to change, such as if a user gets married and wants to change her name.

@NaturalId(mutable=true)
public String getUserName() {
    return userName;
}

I'm also using the Hibernate Validator annotations to restrict the values for some of the members, such as the userName:

@NotEmpty
@Length(min=4,max=16)
@Pattern(regex="^[a-zA-Z\\d_]{4,12}$", message="Invalid screen name.")
private String userName;

Unfortunately, the default localized message for the @Pattern annotation isn't very user-friendly so I've had to resort to a hard-coded English message until I figure out a cleaner solution.

Domain Object Associations

There is a one-to-one relationship between the Preferences and User objects. The Preferences object is loaded with the User object (fetch=FetchType.EAGER) because it contains information that is needed to render the UI for the user.

@OneToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@PrimaryKeyJoinColumn
public Preferences getPreferences() {
    return preferences;
}

There is a bidirectional many-to-many relationship between the User and Role objects using a join table ex_user_role.

@ManyToMany(targetEntity=example.user.Role.class, cascade={CascadeType.PERSIST, CascadeType.MERGE})
@JoinTable(name="ex_user_role",joinColumns=@JoinColumn(name="userId"), inverseJoinColumns=@JoinColumn(name="roleId"))
protected Set<Role> getUserRoles() {
    if (userRoles == null) userRoles = new HashSet<Role>();
    return userRoles;
}

On the Role object, we have:

@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy="userRoles", targetEntity=User.class)
public Set<User> getRoleUsers() {
    if (roleUsers == null) roleUsers = new HashSet<User>();
    return roleUsers;
}

This solution comes directly from the Hibernate documentation, section 7.5.3.

Data Access Object (DAO)

The persistence layer adopts the Data Access Object (DAO) design pattern to hide the details of the underlying persistence mechanism from the business logic of the application. For this solution, there are two DAO interfaces defined: UserDAO and RoleDAO.

The HibernateUserDao implements the UserDAO interface and extends Spring's HibernateDaoSupport base class. HibernateDaoSupport uses the Template method pattern to greatly simplify the process of using Hibernate correctly. Additionally, all checked Hibernate exceptions are translated into unchecked exceptions that extend from org.springframework.dao.DataAccessException. This allows developers to handle most persistence exceptions, which are non-recoverable, only in the appropriate layers, without having annoying boilerplate catch-and-throw blocks and exception declarations in the DAOs. For example, an attempt to insert a new record that violates a primary key constraint can only be resolved by changing the values the end-user is providing, such as a User ID during registration. With the Spring DataAccessException strategy, the DAO will throw a DataIntegrityViolationException and allow the presentation layer to catch it and prompt the end-user to change their input accordingly. Moreover, the Spring DataAccessException strategy insulates our business-logic layer from Hibernate specific classes. This is helpful if the underlying persistence mechanism needs to change for your application.

Hibernate

Each DAO implementation bean is injected with an instance of AnnotationSessionFactoryBean. This Spring FactoryBean provides a shared Hibernate SessionFactory and supports JDK 1.5+ annotation metadata for mappings, which alleviates the need to have hbm.xml files for our persistent objects.

<bean id="hbSessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
  <property name="dataSource" ref="jdbcDs">
  <property name="annotatedClasses">
    <list>
      <value>example.user.User</value>
      <value>example.user.Role</value>
      <value>example.user.Preferences</value>
    </list>
  </property>
  <property name="hibernateProperties">
    <value>hibernate.dialect=example.user.dao.hibernate.HSQLDialect_HHH_1598</value>
  </property>
  <property name="schemaUpdate" value="true">
</bean>

The hbSessionFactory bean is configured to auto-generate the database schema and is injected with our Spring DataSource. The custom hibernate.dialect property is provided to fix an issue with HSQLDB (see: http://forum.hibernate.org/viewtopic.php?p=2324183).

Under the covers, we use an Apache Commons DBCP connection pool that is exposed as a javax.sql.DataSource in the JNDI tree in Tomcat. A javax.sql.DataSource is defined by the JDBC specification to hide connection pooling and transaction management issues from application code. The web-application-config.xml file defines a reference to the javax.sql.DataSource using:

<bean class="org.springframework.jndi.JndiObjectFactoryBean" id="jdbcDs">
  <property name="jndiName" value="java:comp/env/jdbc/exampleDs">
</bean>

The database specific properties for the pool are configured in the META-INF/context.xml file:

<Resource name="jdbc/exampleDs"
          auth="Container"
          type="javax.sql.DataSource"
          username="sa"
          password=""
          driverClassName="org.hsqldb.jdbcDriver"
          url="jdbc:hsqldb:file:example"
          initialSize="0"
          maxActive="10"
          maxIdle="10"
          minIdle="0"
          maxWait="30000"
          poolPreparedStatements="true"
          />

Be sure to put the hsqldb.jar file into the tomcat/lib directory if you use HSQL.

Service Layer

One could allow the presentation layer beans to interact directly with the DAOs. However, this approach leads to your business logic being infected with presentation logic, which makes it difficult to support other interfaces, such as Web services. The Service Layer sits between your presentation layer and persistence layer. The Service Layer is where you implement the business-logic of your application.

For this solution, we have only one Spring bean in the Service Layer: UserService. The UserService provides a high-level interface to the operations provided by the UserDAO and RoleDAO. UserService is an application of the Session Facade design pattern in that it aggregates fine-grained data access operations into a single high-level transaction-aware service. Moreover, the UserService keeps business-logic out of the DAO implementations, which are only concerned with efficient object storage and retrieval.

Transactions

As mentioned above, the UserService is transaction-aware. This is accomplished using a Spring TransactionProxyFactoryBean which works with the HibernateTransactionManager. However, since we're using Spring 2.5.x and Java 5, we can use a more compact, annotation driven approach for making our UserServiceImpl transactional. First, you need to include the following element in your Spring configuration:

<tx:annotation-driven/>

Second, you need to mark your service's concrete implementation class with the @Transactional annotation:

@Transactional
public class UserServiceImpl implements UserService ...

For more information about declarative transactions with Spring, see: Declarative transaction management.

Mail Service

As described in the requirements, we'll need to send emails from time to time. For this, we'll be using Spring's JavaMailSender. The environment specific settings for your SMTP server are pulled from the example-env.properties file.

Presentation Layer

I chose JSF with RichFaces for this project based on the following criteria:

  • Modern, professional looking skin out-of-the-box (I don't want to fool with CSS).
  • Extensive set of easy-to-use, yet flexible UI components, such as trees, data grids, tabbed panes, etc.
  • Easy to create reusable custom components.
  • Large community of users and developers building real apps.
  • Performance: loads and runs fast on all modern browsers.
  • Minimizes hand-coded JavaScript.
RichFaces ModalPanel

I decided to use the RichFaces ModalPanel component <rich:modalPanel> for my dialogs because it helps the user keep their current orientation with the site. Instead of navigating to a different page, I simply open a dialog over the current view. Consequently, I didn't have any need for Spring Web Flow or JSF navigation rules in this example.

Spring-managed JSF Controller Beans

For this solution, there are two Spring-managed JSF controller beans of interest: example.jsf.GuestSupport and example.jsf.UserSession. Here is the definition from the example-jsf.xml file:

<bean id="guest" class="example.jsf.GuestSupport" scope="request">
<property name="userService" ref="userService"/>
<property name="facesSupport" ref="facesSupport"/>
</bean>

The GuestSupport bean (request-scoped) is the controller for actions performed by guests, such as login and register. Spring creates a new instance per request (if needed).

<bean id="userSession" class="example.jsf.UserSession" scope="session">
<property name="userService" ref="userService"/>
<property name="facesSupport" ref="facesSupport"/>
</bean>

The UserSession bean (session-scoped) is the controller for actions performed by authenticated users, such as managing preferences. There will be one instance of UserSession per HttpSession; Spring makes sure a new UserSession bean is created whenever a new HttpSession is created, which happens before the user is logged in. Thus, there are two possible states for the UserSession bean:

  1. User has not been authenticated ( remoteUser == null )
  2. User has logged in ( remoteUser != null )

Thus, you need to make sure not to use this bean in your JSF EL unless the user is logged in.

Registration

When a user clicks on the Register link on the home page, a RichFaces <rich:componentcontrol> is used to open the RichFaces ModalPanel with id registerPanel.

<rich:componentControl for="registerPanel" attachTo="registerLink" operation="show" event="onclick"/>

The form is bound to the doRegister method on our GuestSupport bean using a standard JSF commandButton. Under the covers, JSF uses the SpringBeanELResolver to get an instance of the GuestSupport bean from Spring. This is configured in faces-config.xml:

<application>
  <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
</application>

Registration requires the user to solve a CAPTCHA. The easiest way to add this to your site is to use reCAPTCHA. The best way to get started with reCAPTCHA is to read their developer's guide at http://recaptcha.net/whyrecaptcha.html. I had to use a custom JSF tag to embed reCAPTCHA JavaScript into the registration form because the standard JSF script tag was being written to the page header instead of inline. This is due to the following setting in the web.xml:

<context-param>
  <param-name>com.sun.faces.externalizeJavaScript</param-name>
  <param-value>true</param-value>
</context-param>

This allows browsers to cache our JSF JavaScript instead of having to write it each time the page is accessed, which presumably will help our pages load faster. However, we need the reCAPTCHA script to be inline and not in the header. Fortunately, JSF makes it very easy to create custom tags.

<example:script src="#{uiSupport.recaptchaScriptUrl}">
Strong Password Validation

Notice that the registration form binds the password to the guest.passwordSupport.password.

<h:inputSecret label="#{i18n.login_password}" id="password" required="true" value="#{guest.passwordSupport.password}" size="12" maxlength="12">
  <rich:beanvalidator/>
</h:inputSecret>

This is because the User password is expected to be a one-way MD5 hash instead of plain text. The example.user.PasswordSupport utility bean validates the password using Hibernate Validator:

@NotEmpty
@Length(min=8,max=12)
@Pattern(regex="(?=^.{8,}$)((?=.*\\d)|(?=.*\\W+))(?![.\\n])(?=.*[A-Z])(?=.*[a-z]).*$", message="Password is not secure enough!")
private String password = null;

Upon successful registration, the guest.newUserName and guest.newUserEmail properties are updated on the guest bean. This activates the regPending modal panel to notify the user that their registration must be activated by clicking on an activation link sent to their email. However, if registration fails, then the registerPanel is displayed with the appropriate error message(s).

<a:commandButton id="registerButton" value="#{i18n.register}"
    action="#{guest.doRegister}" styleClass="rich-fileupload-button" reRender="regPending"
    oncomplete="if (document.getElementById('jsfMsgMaxSev').value != '2') { #{rich:component('registerPanel')}.hide(); #{rich:component('regPending')}.show(); }"/>

The oncomplete attribute will close the registerPanel and open the regPending panel if no errors are present in the AJAX response. This relies on the use of an A4J <a:outputPanel>, see index.jsp.

Account Activation

The activation link includes the act parameter containing a hex-encoded MD5 hash code linked to the pending registration. The guest.getUser() method checks for the act parameter and if present tries to activate the user. If successful, the login panel is displayed with the new user name pre-popluated.

Login

For authentication, we delegate to Spring Security using an ExternalContext dispatch to /j_spring_security_check. This handler expects two parameters provided by our Login form: j_username and j_password. Notice that the login form is configured with prependId="false" so that JSF does not add loginForm: to the parameter names. The request to /j_spring_security_check is handled by the springSecurityFilterChain configured in web.xml:

<filter>
  <filter-name>springSecurityFilterChain</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

Spring Security, configured in example-security.xml, uses a userDetailsService based authentication provider. In this case, we configure two simple SQL queries based on the Hibernate schema for the org.springframework.security.userdetails.jdbc.JdbcDaoImpl.

Authenticate by Username and Password:

SELECT userName, password, active from ex_user where userName=?

Get Roles for User:

SELECT ij.userId, r.name FROM ex_role r INNER JOIN (SELECT u.userId, ur.roleId FROM ex_user_role ur, ex_user u WHERE ur.userId=u.userId AND u.userName=?) ij ON r.roleId=ij.roleId

If authentication fails, Spring Security puts the exception into the session keyed by: AbstractProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY. In the JSF layer, we need to check for this exception and if present display a generic JSF error message to the user. It's a good idea to show a generic message because the actual exception may contain information a hacker can use to break into the site. Unfortunately, I had to resort to invoking a static function GuestSupport.setLoginPanelVisibility from a JSP Scriptlet in auth.jspf to check for the Spring Security exception.

Remember Me

Recall that one of our requirements was to support a Remember Me feature so that returning users are automatically logged into the site. With Spring Security, this is trivial; the JSF login form simply needs to pass the _spring_security_remember_me parameter. Under the covers, Spring Security is configured to use the simple hash-based token approach. However, you can also use a more secure solution described here.

JavaScript and AJAX Tricks

In this section, I demonstrate a few of the JavaScript and AJAX tricks used by the solution.

Country / Region

One of the nice things about RichFaces is that you can attach AJAX-driven logic to any JSF component without writing any JavaScript. For example, suppose we want to limit the list of regions based on the selected country on the Preferences form. This is trivial using the <a4j:support> tag provided by RichFaces:

<a4j:support event="onchange" rerender="region" ajaxsingle="true">

This fires an AJAX request when the country is changed and upon success, the region component is re-rendered with a country-specific list of regions.

User name check with AJAX

During registration, if the user's desired screen name is already taken by another user, we should let them know immediately. To do this, we use the <a4j:support> tag provided by RichFaces on the userName <h:inputText> field:

<a4j:support event="onblur" rerender="userName" ajaxsingle="true">

When the onblur event occurs, RichFaces sends an AJAX request to update the guest.registrationScreenName property. If the desired screen name is already taken, a FacesMessage is created to be displayed by the <rich:message> tag associated with the userName field.

<rich:message for="userName">
  <f:facet name="errorMarker"><h:graphicimage value="img/error.gif"></f:facet>
</rich:message>