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>