Spring MVC Form CAPTCHA Code Example

The Spring MVC Basic 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.

Downloaded Location

The Spring MVC Form CAPTCHA Example is included in examples folder of the download package as bdc3-springmvc-form-captcha-example.war file. Deploying (unpacking) the file will create a standard JSP directory tree.

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="botDetect"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Spring MVC Form BotDetect CAPTCHA Example</title>
    <link rel="stylesheet" href="stylesheet.css" type="text/css"/>
  </head>

  <body>
    <h1>Spring MVC Form BotDetect CAPTCHA Example</h1>

    <form:form commandName="index" method="post">
      <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="captchaCodeTextBox" class="prompt">
              Retype the code from the picture:</label>
          <!-- Adding BotDetect Captcha to the page -->
          <botDetect:captcha id="springFormCaptcha"
              codeLength="3"
              imageWidth="150"
              imageStyles="graffiti, graffiti2"/>
          <div class="validationDiv">
            <input id="captchaCodeTextBox" type="text" 
                name="captchaCodeTextBox" 
                value="${message.captchaCodeTextBox}"/><br>
          </div>
        </c:if>
        <input type="submit" name="submit" value="Submit" />&nbsp;
      </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 botdetect.examples.springmvc.form.controller;

import 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 botdetect.examples.springmvc.form.model;

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

public class Message {
  private String name, email, message, userCaptchaCode;
  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 getCaptchaCodeTextBox() {
    return userCaptchaCode;
  }

  public void setCaptchaCodeTextBox(String userCaptchaCode) {
    this.userCaptchaCode = userCaptchaCode;
  }

  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 botdetect.examples.springmvc.form.validator;

import botdetect.examples.springmvc.form.model.Message;
import botdetect.web.Captcha;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

public class MessageValidator implements Validator {

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

  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.getCaptchaCodeTextBox())){
      errors.rejectValue("captchaCodeTextBox", "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 userCaptchaCode){
    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(request, userCaptchaCode);
    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 PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Spring MVC Form BotDetect CAPTCHA Example</title>
    <link rel="stylesheet" href="stylesheet.css" type="text/css"/>
  </head>
  <body>
    <h1>Spring MVC Form BotDetect CAPTCHA Example</h1>
    <h2>View Messages</h2>
    <form:form commandName="messages" method="post">
      <c:forEach var="message" items="${messages.messagesList}">
        <p class="message">
        <c:out value="${message}"/><br>
        </p>
      </c:forEach>
      <input type="submit" name="clear" value="Clear messages" />
      <input type="submit" name="add" value="Add message" />
    </form:form>
  </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 botdetect.examples.springmvc.form.controller;

import 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.

MessagesList.java

package 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.

To run this example, botdetect.jar must be in the classpath.

Please Note

BotDetect Java Captcha Library v4.0.Beta3 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.