Spring 2 Form CAPTCHA Code Example

The Spring MVC 2 Form Captcha code example shows how to add BotDetect CAPTCHA protection to a typical Spring MVC form.

Captcha validation is integrated with other form fields validation, and only submissions that meet all validation criteria are accepted.

If the Captcha is successfully solved but other field validation fails, the Captcha is hidden since the users have already proven they are human.

This kind of validation could be used on various types of public forms which accept messages, and are at risk of unwanted automated submissions.

For example, it could be used to ensure bots can't submit anything to a contact form, add guestbook entries, blog post comments or anonymous message board / forum replies.

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-spring2-contact_form/ folder; and contains the following files:

Example files

Running Example

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 contentType="text/html" pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@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: Spring MVC 2 Form CAPTCHA Code Example</title>
  <link rel="stylesheet" href="stylesheet.css" type="text/css" />
</head>
<body>
  <form:form commandName="index" method="post" class="column" id="form1">
    <h1>BotDetect Java CAPTCHA Validation:<br> Spring MVC 2 Form CAPTCHA Code Example</h1>
    <fieldset>
      <legend>CAPTCHA included in Spring MVC form validation</legend>
      <div class="input">
        <label for="name">Name:</label>
        <form:input path="name" cssClass="textbox" /><br>
        <form:errors path="name" cssClass="incorrect"/>
      </div>

      <div class="input">
        <label for="email">Email:</label>
        <form:input path="email" cssClass="textbox" /><br>
        <form:errors path="email" cssClass="incorrect"/>
      </div>

      <div class="input">
        <label for="message">Short message:</label>
        <form:textarea path="message" cssClass="inputbox" rows="5" cols="40" /><br>
        <form:errors path="message" cssClass="incorrect"/>
      </div>

      <c:if test="${!index.captchaVerified}">
        <label for="captchaCode" class="prompt">Retype the characters from the picture:</label>

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

        <div class="validationDiv">
          <input id="captchaCode" type="text" name="captchaCode" value="${message.captchaCode}"/><br>
          <form:errors path="captchaCode" cssClass="incorrect"/>
        </div>
      </c:if>

      <input type="submit" name="submitButton" id="submitButton" value="Submit" />
    </fieldset>
  </form:form>
</body>
</html>

Beside the Captcha code input textbox, the example form contains three other fields, which are all validated the same way in form processing code.

To hide the Captcha challenge after the user solves it (regardless of other field validation status), the Captcha object's Html value is only added to the page if not already solved (<c:if test="${!index.captchaVerified}">).

IndexController.java

package com.captcha.botdetect.examples.springmvc.form.controller;

import com.captcha.botdetect.examples.springmvc.form.model.Message;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;

public class IndexController extends SimpleFormController {

  public IndexController() {
    setCommandClass(Message.class);
    setCommandName("index");
  }

  @Override
  protected Object formBackingObject(HttpServletRequest request) {
    Message message = new Message();
    message.setRequest(request);
    return message;
  }

  @Override
  public ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response,
      Object command, BindException errors) {
    HttpSession session = request.getSession();
    if (session != null) {
      Message message = (Message) command;
      saveMessage(session, message.getName(), message.getEmail(), message.getMessage());
      session.removeAttribute("captchaVerified");
      return new ModelAndView(getSuccessView());
    } else {
      return new ModelAndView(getFormView());
    }
  }

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

There is no BotDetect-specific code in the index page controller, since all Captcha verification is done through a MessageValidator. When all form fields have passed validation and the protected action successfully executed, we can reset Captcha validation status saved in the Session flag. This way, the protected action can only be perfomed once for each passed Captcha challenge.

Message.java

package com.captcha.botdetect.examples.springmvc.form.model;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

public class Message {
  
  private String name, email, message, captchaCode;
  private HttpServletRequest request;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getEmail() {
    return email;
  }

  public void setEmail(String email) {
    this.email = email;
  }

  public String getMessage() {
    return message;
  }

  public void setMessage(String message) {
    this.message = message;
  }

  public String getCaptchaCode() {
    return captchaCode;
  }

  public void setCaptchaCode(String captchaCode) {
    this.captchaCode = captchaCode;
  }

  public HttpServletRequest getRequest() {
    return request;
  }

  public void setRequest(HttpServletRequest request) {
    this.request = request;
  }

  public boolean isCaptchaVerified() {
    if (this.request == null) {
      return false;
    }
    
    HttpSession session = request.getSession();
    if (session == null) {
      return false;
    }
    
    return (session.getAttribute("captchaVerified") != null);
  }

}

A Command object for index form.

MessageValidator.java

package com.captcha.botdetect.examples.springmvc.form.validator;

import com.captcha.botdetect.examples.springmvc.form.model.Message;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

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

public class MessageValidator implements Validator {

  @Override
  public boolean supports(Class type) {
    return Message.class.isAssignableFrom(type);
  }

  @Override
  public void validate(Object o, Errors errors) {
    Message message = (Message)o;
    if (!isValidName(message.getName())) {
      errors.rejectValue("name", "length", "*");
    }

    if (!isValidEmail(message.getEmail())) {
      errors.rejectValue("email", "format", "*");
    }

    if (!isValidMessage(message.getMessage())) {
      errors.rejectValue("message", "length", "*");
    }

    if (!isCaptchaValid(message.getRequest(), message.getCaptchaCode())) {
      errors.rejectValue("captchaCode", "captcha", "*");
    }
  }

  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 boolean isCaptchaValid(HttpServletRequest request, String captchaCode) {
    HttpSession session = request.getSession();
    if ((session != null) && (session.getAttribute("captchaVerified") != null)) {
      return true;
    }
    // validate the Captcha to check we're not dealing with a bot
    Captcha captcha = Captcha.load(request, "springFormCaptcha");
    boolean isHuman = captcha.validate(captchaCode);
    if (isHuman) {
      if (session == null) {
        session = request.getSession(true);
      }
      session.setAttribute("captchaVerified", true);
      return true;
    } else {
      return false;
    }
  }

}

To validate BotDetect Captcha with a Spring MVC Validator just put (or invoke) the validation code within the Validator's validate(Object o, Errors errors) method and provide the same Captcha id as on the index form.

messages.jsp

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>BotDetect Java CAPTCHA Validation: Spring MVC 2 Form CAPTCHA Code Example</title>
  <link rel="stylesheet" href="stylesheet.css" type="text/css" />
</head>
<body>
  <div class="column">
    <h1>BotDetect Java CAPTCHA Validation:<br> Spring MVC 2 Form CAPTCHA Code Example</h1>
    <h2>View Messages</h2>
    <form:form commandName="messages" method="post">
      <c:choose>
        <c:when test="${messages.messagesList.size() > 0}">
          <c:forEach var="message" items="${messages.messagesList}">
            <p class="message">
              <c:out value="${message}"/><br>
            </p>
          </c:forEach>
        </c:when>
        <c:otherwise>
          <p class="message">No messages yet.</p>
        </c:otherwise>
      </c:choose>       
      
      <input type="submit" name="add" value="Add Message" />
    </form:form>
  </div>  
</body>
</html>

This page displays all successfully submitted messages, and the user is automatically redirected here after validation of all form fields (including Captcha) is passed.

MessagesController.java

package com.captcha.botdetect.examples.springmvc.form.controller;

import com.captcha.botdetect.examples.springmvc.form.model.MessagesList;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;

public class MessagesController extends SimpleFormController {

  public MessagesController() {
    setCommandClass(MessagesList.class);
    setCommandName("messages");
  }

  @Override
  protected Object formBackingObject(HttpServletRequest request) {
    List<String> messages = new ArrayList<String>();
    HttpSession session = request.getSession();
    if (session != null) {
      Enumeration attributes = session.getAttributeNames();
      while (attributes.hasMoreElements()) {
        String attribute = (String) attributes.nextElement();
        if (attribute.startsWith("Message_")) {
          messages.add((String) session.getAttribute(attribute));
        }
      }
    }
    MessagesList messagesList = new MessagesList();
    messagesList.setMessagesList(messages);
    return messagesList;
  }

  @Override
  protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response,
      Object command, BindException errors) {
    if (request.getParameter("clear") != null) {
      HttpSession session = request.getSession();
      if (session != null) {
        session.invalidate();
      }
      return new ModelAndView("messages");
    } else {
      return new ModelAndView("redirect:index.htm");
    }
  }
}

Controller for messages.jsp. As, messages are stored in session state for simplicity, we just invalidate the session to clear existing messages.

MessageList.java

package com.captcha.botdetect.examples.springmvc.form.model;

import java.util.ArrayList;
import java.util.List;

public class MessagesList {
  
  private List<String> messages = new ArrayList<String>();

  public void setMessagesList(List messages) {
    this.messages = messages;
  }

  public List<String> getMessagesList() {
    return messages;
  }

}

A Command object for messages form.

web.xml

Note: Here is how to register BotDetect servlet in case you are using Spring Boot.

<?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">
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>2</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>*.htm</url-pattern>
  </servlet-mapping>

  <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>   
  
  <session-config>
    <session-timeout>
      30
    </session-timeout>
  </session-config>
  <welcome-file-list>
    <welcome-file>redirect.jsp</welcome-file>
  </welcome-file-list>
</web-app>

In WEB-INF/web.xml file we register CaptchaServlet used for BotDetect Captcha requests.


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.