Password Confirmation on a JSF Page – Part 2 An Entity Model

Kyle Stiemann from the Liferay Faces Team was kind enough to point out a flaw in the Java Server Faces libraries prior to versions JSF 2.3 and Mojarra 2.2.16 related to what I am presenting here. The solution requires two changes. The first is an addition to the web.xml file that you will see commented about in the file. The second change requires using newer versions JSF. Payara 4/Glassfish 4 manifests the problem. Payara 5/Glassfish 5, with the newer library, does not exhibit the problem. You can read about the problem at: JSFSPEC-1433 issue

Welcome to part 2 of 2 articles on confirming password entry into a JSF form. You should read part 1 at https://www.omnijava.com/2018/04/24/password-confirmation-on-a-jsf-page-part-1-a-simple-model/ to understand how I got to this point. In this example rather than a plain bean we will use the JPA with an Entity bean and a JPA controller as generated by my IDE of choice, NetBeans 8.2. In addition to using the JPA to save the login name and password, there is a new validator to ensure that the login name does not already exist in the database.

To run this code you will need MySQL or other SQL database. The scripts for creating the database and table are in src/test/resources. These must be run before you can execute the code.

You can download the project at https://gitlab.com/omniprof/JSFPasswordConfirmationEntityBean.git

To begin with let’s look at the NetBeans 8.2 generated Entity class:

import java.io.Serializable;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

@Entity
@Table(name = "user_table", catalog = "JSF_PASSWORD", schema = "")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;
    @Id
    @Basic(optional = false)
    @NotNull
    @Size(min = 1, max = 45)
    @Column(name = "LOGIN_NAME")
    private String loginName;
    @Basic(optional = false)
    @NotNull
    @Size(min = 1, max = 12)
    @Column(name = "PASSWORD")
    private String password;

    public User() {
    }

    public User(String loginName) {
        this.loginName = loginName;
    }

    public User(String loginName, String password) {
        this.loginName = loginName;
        this.password = password;
    }

    public String getLoginName() {
        return loginName;
    }

    public void setLoginName(String loginName) {
        this.loginName = loginName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (loginName != null ? loginName.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof User)) {
            return false;
        }
        User other = (User) object;
        if ((this.loginName == null && other.loginName != null) || (this.loginName != null && !this.loginName.equals(other.loginName))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "com.jsfpasswordconfirm.entities.UserTable[ loginName=" + loginName + " ]";
    }
    
}
User.java

This entity class is not under the management of CDI. Therefore the backing bean will be responsible for creating the entity bean in the getUser() method. The JPA controller class will be managed by CDI and is injected into the backing bean.

You should take note of the fact that JSR-303 Bean Validation annotations are used. Specifically, each field has an @Size annotation that comes from JSR-303. The same validation is also present on the JSF form using f:validateLength. Here is where I stumbled across an unusual behavior.

When I leave the fields blank I trigger the JSF required validation and the appropriate message from the bundle is displayed. When I exceed the length of the fields, 45 for login name and 12 for password something unusual occurs. I get two messages, one from the JSF validator and one from JSR-303. This does not make sense to me because I expect the JSR-303 validation to occur only if the JSF validation is successful and we apply the UI elements to the model. Searching for why this is occurring has not turned up a reason. If you know why this is happening please let me know.

Double validation message

There are two ways to handle this problem. The first is to remove all validation from the JSF form that matches a JSR-303 validation and only have JSR-303 validation in the bean. While this works it moves the validation away from the user interface. I prefer to validate closer to the user. The opposite approach, remove the JSR-303 validation from the bean, should never be done. Entity beans are filled not just from the user interface but from the database and possibly other classes in the application. There must be bean validation.

The solution I discovered is simple. You can prevent bean validation from occurring by adding:

<f:validateBean disabled="true" />

You only need to add this to the login name and password. The password confirm field is not in the entity so it is not subject to JSR-303 validation. Here is the JSF page.

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:f="http://xmlns.jcp.org/jsf/core">

    <h:head>
        <title>#{msgs.pageTitle}</title>
        <h:outputStylesheet library="css" name="styles.css" />
    </h:head>
    <h:body>
        <h1>#{msgs.pageHeader1}</h1>
        <h:form>
            <h:panelGrid columns="3" cellpadding="1" border="0" cellspacing="10"
                         title="#{msgs.signup}" columnClasses="firstColumn,secondColumn,thirdColumn"
                         rowClasses="rowHeight, rowHeight">
                <f:facet name="header">
                    <h:outputText value="#{msgs.signup}" />
                </f:facet>

                <h:outputLabel for="login_name" value="#{msgs.login_name}"/>
                <h:inputText id="login_name" 
                             value="#{passwordBacking.user.loginName}"
                             required="true"
                             requiredMessage="#{msgs.required}" 
                             validator="#{passwordBacking.validateUniquePassword}">
                    <f:validateLength maximum="45" />
                    <f:validateBean disabled="true" />
                </h:inputText>
                <h:messages for="login_name" styleClass="message" />

                <h:outputLabel for="password" value="#{msgs.password}"/>
                <h:inputText id="password" label="#{msgs.age}"
                             value="#{passwordBacking.user.password}"
                             required="true"
                             requiredMessage="#{msgs.required}">
                    <f:validateLength maximum="12" />
                    <f:validateBean disabled="true" />
                </h:inputText>
                <h:messages for="password" styleClass="message" />

                <h:outputLabel for="password_confirm" value="#{msgs.confirm}"/>
                <h:inputText id="password_confirm"
                             value="#{passwordBacking.passwordConfirm}"
                             required="true"
                             requiredMessage="#{msgs.required}"
                             validator="#{passwordBacking.validatePasswordCorrect}" >
                    <f:validateLength maximum="12" />
                </h:inputText>
                <h:messages for="password_confirm" styleClass="message" />

            </h:panelGrid>
            <h:commandButton id="saveButton" value="#{msgs.save}"
                             action="#{passwordBacking.createUser()}"
                             styleClass="myButton"/>
        </h:form>
    </h:body>
</html>
index01.xhtml

I have added one additional validator. The login name is the primary key. Therefore, it is necessary to test if the login name is already in the database.

validator="#{passwordBacking.validateUniquePassword}"

In the backing bean I have added the method validateUniquePassword. Here is the updated backing bean.

import com.jsfpasswordconfirm.controller.UserTableJpaController;
import com.jsfpasswordconfirm.entities.User;
import java.io.Serializable;
import javax.enterprise.context.RequestScoped;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.validator.ValidatorException;
import javax.inject.Inject;
import javax.inject.Named;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Named("passwordBacking")
@RequestScoped
public class PasswordBackingBean implements Serializable {

    private final static Logger LOG = LoggerFactory.getLogger(PasswordBackingBean.class);

    @Inject
    private UserTableJpaController controller;

    // The entity object
    private User user;
    // The value for the confirmPassword field that does not exist in the entity
    private String passwordConfirm;

    public User getUser() {
        LOG.trace("getUser");
        if (user == null) {
            LOG.debug("Creating new User entity object");
            user = new User();
        }
        return user;
    }

    /**
     * Save the current person to the db
     *
     * @return
     * @throws Exception
     */
    public String createUser() throws Exception {
        LOG.trace("Creating new User entity object");
        controller.create(user);
        return "doSomething";
    }

    public String getPasswordConfirm() {
        LOG.trace("getPasswordConfirm");
        return passwordConfirm;
    }

    public void setPasswordConfirm(String passwordConfirm) {
        LOG.trace("setPasswordConfirm");
        this.passwordConfirm = passwordConfirm;
    }

    public void validatePasswordCorrect(FacesContext context, UIComponent component,
            Object value) {

        LOG.trace("validatePasswordCorrect");

        // Retrieve the value passed to this method
        String confirmPassword = (String) value;

        // Retrieve the temporary value from the password field
        UIInput passwordInput = (UIInput) component.findComponent("password");
        String password = (String) passwordInput.getLocalValue();

        LOG.debug("validatePasswordCorrect: " + password + " and " + confirmPassword);
        if (password == null || confirmPassword == null || !password.equals(confirmPassword)) {
            String message = context.getApplication().evaluateExpressionGet(context, "#{msgs['nomatch']}", String.class);
            FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, message, message);
            throw new ValidatorException(msg);
        }
    }

    public void validateUniquePassword(FacesContext context, UIComponent component,
            Object value) {

        LOG.trace("validateUniquePassword");

        // Retrieve the value passed to this method
        String loginName = (String) value;

        LOG.debug("validateUniquePassword: " + loginName);
        if (controller.findUser(loginName) != null) {
            String message = context.getApplication().evaluateExpressionGet(context, "#{msgs['duplicate']}", String.class);
            FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, message, message);
            throw new ValidatorException(msg);
        }
    }

}
PasswordBackingBean.java

As mentioned earlier in this article the getUser() method creates the User entity, it is not injected. The validateUniquePassword method calls upon the JPA controller’s findUser method that either returns an entity or a null reference.

Everything is in place now. The form now validates that there is input in the fields, validates the length of the fields, validates that entry of the password in the two password fields is the same and validates that the login name will be unique in the database.

About Ken Fogel

Chairperson and Program Coordinator for the Computer Science Technology Program at Dawson College, Montreal. Instructor at the Continuing Education Computer Institute of Concordia University, Montreal. Passionate about teaching and inspiring students to be better programmers. Member of the NetBeans Dream Team.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.