JavaServer Pages jQuery CAPTCHA Code Example

This document describe old, BotDetect's integration into jQuery/JSP application using the BotDetect Java Traditional API. Nowadays, we developed BotDetect Captcha jQuery Plugin which uses Simple API and we recommend you to use it instead.

..............................................................

The JSP jQuery Captcha code example shows how to integrate BotDetect CAPTCHA validation with jQuery Validation client-side form validation.

It uses the Captcha Form Example as a starting point, and adds client-side jQuery Validation rules for all form fields.

Client-side validation is not secure by itself (it can be bypassed trivially), so the example also shows how the protected form action must always be secured by server-side CAPTCHA validation first, and use client-side validation only to improve the user experience.

Download the BotDetect Java CAPTCHA Generator archive to run this example

Within this page, the root folder of the extracted archive is referred as the <BDC-DIR>.

This example is in the <BDC-DIR>/examples/t_api-captcha-jsp2-contact_form-jquery/ folder; and contains the following files:

Running Example

This example's war file (in BotDetect download package) already embeds all dependencies.

In case you are making this example from scratch in your IDE, you need to ensure botdetect.jar, botdetect-servlet.jar, and botdetect-jsp20.jar are in the classpath.

index.jsp

<%@page trimDirectiveWhitespaces="true"%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="botDetect" uri="https://captcha.com/java/jsp"%>
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>BotDetect Java CAPTCHA Validation: JSP jQuery Validation CAPTCHA Code Example</title>
  <link rel="stylesheet" href="stylesheet.css" type="text/css" />
  <script type="text/javascript" src="js/jquery-1.7.2.min.js"></script>
  <script type="text/javascript" src="js/jquery.validate.min.js"></script>
</head>
<body>
  <form method="post" action="jqueryValidationAction" class="column" id="form1">
    <h1>BotDetect Java CAPTCHA Validation:<br> JSP jQuery Validation CAPTCHA Code Example</h1>
    <fieldset>
      <legend>CAPTCHA included in JSP form validation</legend>
      <div class="input">
        <label for="name">Name:</label>
        <input type="text" name="name" class="textbox" value="${param.name}" />
        <span class="incorrect">${messages.nameIncorrect}</span>
      </div>

      <div class="input">
        <label for="email">Email:</label>
        <input type="text" name="email" class="textbox" value="${param.email}" />
        <span class="incorrect">${messages.emailIncorrect}</span>
      </div>

      <div class="input">
        <label for="message">Short message:</label>
        <textarea class="inputbox" name="message" rows="5" cols="40">${param.message}</textarea>
        <span class="incorrect">${messages.messageIncorrect}</span>
      </div>

      <%
        if (request.getSession().getAttribute("isCaptchaSolved") == null) {
      %>
          <label for="captchaCode" class="prompt">Retype the characters from the picture:</label>

          <!-- Adding BotDetect Captcha to the page -->
          <botDetect:captcha id="jQueryValidatedCaptcha" 
                userInputID="captchaCode"
                codeLength="3"
                imageWidth="150"
                imageStyle="GRAFFITI2" />

          <div class="validationDiv">
            <input id="captchaCode" type="text" name="captchaCode" />
            <span class="incorrect">${messages.captchaIncorrect}</span>
          </div>
      <%
        }
      %>

      <input type="submit" name="submitButton" id="submitButton" value="Submit" />
      <span class="correct">${messages.captchaCorrect}</span>
    </fieldset>
    
    <script type="text/javascript" src="js/validation-rules.js"></script>
    
  </form>
</body>
</html>

The input form code is similar to Captcha tag example index.jsp. Differences are that we must include jQuery, the jQuery.validate plugin and the script containing our custom validation rules.

validation-rules.js

$(document).ready(function() {
  $("#form1").validate({
    rules: {
      name: { required: true, minlength: 3 },
      email: { required: true, email: true },
      message: { required: true, minlength: 10 },
      // Captcha code is a required input, and its value is validated remotely
      // the remote validation Url is exposed through the BotDetect client-side API
      captchaCode: { required: true, remote: $("#captchaCode").get(0).Captcha.ValidationUrl }
    },
    messages: {
      name: {
        required: "A name is required",
        minlength: jQuery.format("Enter at least {0} characters")
      },
      email: {
        required: "An email address is required",
        email: "Please enter a valid email address"
      },
      message: {
        required: "A message is required",
        minlength: jQuery.format("Enter at least {0} characters")
      },
      // error messages for Captcha code validation 
      captchaCode: {
        required: "The Captcha code is required",
        remote: "The Captcha code must be retyped correctly"
      }
    },
    // the Captcha input must only be validated when the whole code string is
    // typed in, not after each individual character (onkeyup must be false)
    onkeyup: false,
    // validate user input when the element loses focus
    onfocusout: function(element) { $(element).valid(); },
    // reload the Captcha image if remote validation failed
    showErrors: function(errorMap, errorList) {
      if (typeof(errorMap.captchaCode) != "undefined" &&
          errorMap.captchaCode === this.settings.messages.captchaCode.remote) {
        $("#captchaCode").get(0).Captcha.ReloadImage();
      }
      this.defaultShowErrors();
    },
    success: function(label) {
      label.text("Ok!");
      label.addClass(this.validClass);
    },
    errorClass: "incorrect",
    validClass: "correct",
    errorElement: "label"
  });
});

To set up jQuery validation of a textbox taking Captcha code input, we do the following:

  • Add validation rules specifying it as a required field, that also needs to be validated remotely when a value is entered; define error messages shown when these validation rules are triggered
  • Disable onkeyup validation of the Captcha code, since we must validate the whole code and not the individual characters
  • Reload the Captcha image whenever remote Captcha validation fails

CAPTCHA jQuery Validation Rules and Error Messages

Since we're not using the input textbox Id for jQuery validation rules, we need to add them dynamically after the default validator has been initialized; that's why they're located after the $("#form1").validate({ call.

The expression for the remote Captcha validation Url can easily be read looking at dot-separated segments from right to left:

  • The ValidationUrl property is part of the BotDetect client-side API, and is unique for each Captcha instance
  • The client-side BotDetect object can always be accessed through the Captcha property of the Captcha code textbox.
  • We use the standard jQuery selector to access the textbox by Css class, and the .get(0) function call to get the underlying DOM element. This is needed because BotDetect adds the custom Captcha property to the DOM element directly, and the jQuery wrapper element returned by the jQuery selector doesn't include it.

Disabling Remote CAPTCHA Validation on Individual Character Input

Since BotDetect deletes the Captcha code stored on the server after failed validation of a particular Captcha instance, we must avoid validating the user input before the user finished typing in the whole Captcha code. The simplest way to achieve this is to disable onkeyup validation completely (onkeyup: false).

Reloading the CAPTCHA Image When Remote Validation Fails

Due to the above (failed validation invalidates the stored code for security purposes), we must also always reload the Captcha image when remote validation fails. Otherwise the user would be trying to correct his input based on an expired image, and couldn't pass Captcha validation at all.

We do this by customizing the jQuery.validate showErrors callback: beside the regular functionality (this.defaultShowErrors), we also check that there is a Captcha validation error (typeof(errorMap.captchaCode) != "undefined") and that the error message indicates a remote validation failure (errorMap.captchaCode === this.settings.messages.captchaCode.remote). If that is case, we call the ReloadImage() function on the client-side Captcha object (accessed as explained above).

JQueryValidationAction.java

package com.captcha.botdetect.examples.jsp.jquery_validation;

import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.captcha.botdetect.web.servlet.Captcha;

public class JQueryValidationAction extends HttpServlet {
  
  HttpSession session;

  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

    Map<String, String> messages = new HashMap<String, String>();
    request.setAttribute("messages", messages);
    
    Captcha captcha = Captcha.load(request, "jQueryValidatedCaptcha");
    boolean messageValid = true;
    String destinationPage = "/index.jsp";

    session = request.getSession(true);

    if (session.getAttribute("isCaptchaSolved") == null) {
      // validate the Captcha to check we're not dealing with a bot
      boolean isHuman = captcha.validate(request.getParameter("captchaCode"));
      if (isHuman) {
        // Captcha validation passed, perform protected action
        session.setAttribute("isCaptchaSolved", true);
      } else {
        // Captcha validation failed, show error message
        messages.put("captchaIncorrect", "*");
        messageValid = false;
      }
    }

    if (!isValidName(request.getParameter("name"))) {
      messages.put("nameIncorrect", "*");
      messageValid = false;
    }

    if (!isValidEmail(request.getParameter("email"))) {
      messages.put("emailIncorrect", "*");
      messageValid = false;
    }

    if (!isValidMessage(request.getParameter("message"))) {
      messages.put("messageIncorrect", "*");
      messageValid = false;
    }

    if (messageValid) {
      saveMessage(request.getParameter("name"), request.getParameter("email"), request.getParameter("message"));
      session.removeAttribute("isCaptchaSolved");
      destinationPage = "/messages.jsp";
    }

    RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(destinationPage);
    dispatcher.forward(request, response);
  }

  private boolean isValidName(String name) {
    if (name == null) {
      return false;
    }
    return name.length() > 2 && name.length() < 30;
  }

  private boolean isValidEmail(String email) {
    if (email == null) {
      return false;
    }
    return email.matches("^[\\w-_\\.+]*[\\w-_\\.]\\@([\\w]+\\.)+[\\w]+[\\w]$");
  }

  private boolean isValidMessage(String message) {
    if (message == null) {
      return false;
    }
    return (message.length() > 2) && (message.length() < 255);
  }

  private void saveMessage(String name, String email, String messageText) {
    Date timeStamp = new Date();
    String message = name + " (" + email + ") says: " + messageText;
    session.setAttribute("Message_" + timeStamp.getTime(), message);
  }
}

We left the server-side Captcha validation in place, ensuring that any bots or users with JavaScript disabled have their input checked. For users with JavaScript enabled, errors in Captcha code input will be shown on the client without full form being POSTed to the server.

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee                 http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  <session-config>
    <session-timeout>
      30
    </session-timeout>
  </session-config>
  
  <servlet>
    <servlet-name>BotDetect Captcha</servlet-name>
    <servlet-class>com.captcha.botdetect.web.servlet.CaptchaServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>BotDetect Captcha</servlet-name>
    <url-pattern>/botdetectcaptcha</url-pattern>
  </servlet-mapping>
  
  <context-param>
    <param-name>javax.faces.VALIDATE_EMPTY_FIELDS</param-name>
    <param-value>true</param-value>
  </context-param>
  
  <servlet>
    <servlet-name>JQueryValidationAction</servlet-name>
    <servlet-class>com.captcha.botdetect.examples.jsp.jquery_validation.JQueryValidationAction</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>JQueryValidationAction</servlet-name>
    <url-pattern>/jqueryValidationAction</url-pattern>
  </servlet-mapping>
  
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

In WEB-INF/web.xml file we register CaptchaServlet used for BotDetect Captcha requests and register JQueryValidationAction servlet used for validating form data.


Please Note

BotDetect Java Captcha Library v4.0.Beta3.7 is an in-progress port of BotDetect 4 Captcha, and we need you to guide our efforts towards a polished product. Please let us know if you encounter any bugs, implementation issues, or a usage scenario you would like to discuss.