Gui authentication: The real McCoy

exercise No. 51

Q:

We now implement a refined version to enter Person records based on the solutions of two related exercises:

Input validation by regular expressions

Avoiding SQL injection by sanitizing user input

Prepared Statements to keep the barbarians at the gate

Avoiding SQL injection by using java.sql.PreparedStatement objects.

A better solution should combine both techniques. Non-vulnerability a basic requirement. Checking an E-Mail for minimal conformance is an added value.

In order to address authentication the relation Person has to be extended appropriately. The GUI needs two additional fields for login name and password as well. The following video demonstrates the intended behaviour:

Figure 950. Intended usage behaviour for insertion of data records.

Don't forget to use password hashes like those from the section called “Passwords and hash values”. Due to their length you may want to consider the data type TEXT.

A:

In comparison to earlier versions it does make sense to add some internal container structures. First we note, that each GUI input field requires:

  • A label like Enter password.

  • A corresponding field object to hold user entered input.

  • A validator checking for correctness of entered data.

  • A label or text field for warning messages in case of invalid user input.

First we start by grouping label ❶, input field's verifier ❷ and the error message label ❸ in sda.jdbc.intro.auth.UserInputUnit:

package sda.jdbc.intro.auth;
...
public class UserInputUnit {

   final JLabel label; final InputVerifierNotify verifier; final JLabel errorMessage; public UserInputUnit(final String guiText, final InputVerifierNotify verifier) {
      this.label = new JLabel(guiText);
      this.verifier = verifier;
      errorMessage = new JLabel();
   } ...

The actual GUI text field is being defined ❶ in class sda.jdbc.intro.auth.InputVerifierNotify:

package sda.jdbc.intro.auth;
...
public abstract class InputVerifierNotify extends InputVerifier {

   protected final String errorMessage;
   public final JLabel validationLabel;
   public final JTextField field; public InputVerifierNotify(final JTextField field, final String errorMessage) { ...

We need two field verifier classes being derived from sda.jdbc.intro.auth.InputVerifierNotify:

sda.jdbc.intro.auth.RegexpVerifier

This one is well known from earlier versions and is used to validate text input fields by regular expressions.

sda.jdbc.intro.auth.InputVerifierNotify

This verifier class is responsible for comparing our two password fields to have identical values.

All these components get assembled in sda.jdbc.intro.auth.InsertPerson. We remark some important points:

package sda.jdbc.intro.auth;
...
public class InsertPerson extends JFrame {
... // GUI attributes for user input
   final UserInputUnit name = 
         new UserInputUnit(
               "Name",
               new RegexpVerifier(new JTextField(15), "^[^;'\"]+$",
                                        "No special characters allowed"));

   // We need a reference to the password field to avoid
   // casting from JTextField later.
   private final JPasswordField passwordField = new JPasswordField(10); 
   private final UserInputUnit password =
         new UserInputUnit(
               "Password",
               new RegexpVerifier(passwordField, "^.{6,20}$",
                                       "length from 6 to 20 characters"));
...
   private final UserInputUnit passwordRepeat =
         new UserInputUnit(
               "repeat pass.",
               new EqualValueVerifier  (new JPasswordField(10),
                                passwordField, "Passwords do not match"));

   private final UserInputUnit [] userInputUnits = 
      {name, email, login, password, passwordRepeat};
...
   private void userLoginDialog() {...}
...
   public InsertPerson (){
...
      databaseFieldPanel.setLayout(new GridLayout(0, 3)); //Third column for
      add(databaseFieldPanel);                           // validation label

      for (UserInputUnit unit: userInputUnits) { 
         databaseFieldPanel.add(unit.label);
         databaseFieldPanel.add(unit.verifier.field);
         databaseFieldPanel.add(unit.verifier.validationLabel);
      }
      insertButton.addActionListener(new ActionListener() {
         @Override public void actionPerformed(ActionEvent e) {
            if (inputValuesAllValid()) {
               if (persistenceHandler.add( 
                     name.getText(),
                     email.getText(),
                     login.getText(),
                     passwordField.getPassword())) {
                  clearMask();
...}
   private void clearMask() {  
      for (UserInputUnit unit: userInputUnits) {
         unit.verifier.field.setText("");
         unit.verifier.clear();
      }
   }
   private boolean inputValuesAllValid() {
      for (UserInputUnit unit: userInputUnits) {
         if (!unit.verifier.verify(unit.verifier.field)){
            return false;
         }
      }
      return true;
   }
}

All GUI related stuff for entering a user's name

Password fields need special treatment: getText() is superseded by getPassword(). In order to avoid casts from javax.swing.JTextField to javax.swing.JPasswordField we simply keep an extra reference.

In order to check both password fields for identical values we need a different validator sda.jdbc.intro.auth.EqualValueVerifier expecting both password fields in its constructor.

All 5 user input elements get grouped by an array. This allows for iterations like in or .

Adding all GUI elements to the base pane in a loop.

Providing user entered values to the persistence provider.

Whenever a dataset has been successfully sent to the database we have to clean our GUI to possibly enter another record.

Thanks to our grouping aggregation of individual input GUI field validation states becomes easy.