Spring 5 Form CAPTCHA Code Example

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

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-spring5-contact_form/ 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.

contact.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" %>
<%@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 5 Form CAPTCHA Code Example</title>
  <link rel="stylesheet" href="stylesheet.css" type="text/css" />
</head>
<body>
  <form:form modelAttribute="contact" action="add-contact" method="POST" cssClass="column" id="form1">
    <h1>BotDetect Java CAPTCHA Validation:<br> Spring MVC 5 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="subject">Subject:</label>
        <form:textarea path="subject" cssClass="inputbox" rows="5" cols="40" /><br>
        <form:errors path="subject" cssClass="incorrect"/>
      </div>

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

      <c:if test="${!contact.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" /><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="${!contact.captchaVerified}">).

ContactController.java

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

import com.captcha.botdetect.examples.springmvc.form.model.Contact;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class ContactController {
  
  @Autowired
  @Qualifier("contactValidator")
  private Validator validator;
  
  @InitBinder
  private void initBinder(WebDataBinder binder) {
    binder.setValidator(validator);
  }
  
  @ModelAttribute("contact")
  public Contact createContactModel(HttpServletRequest request) {
    Contact contact = new Contact();
    contact.setHttpRequest(request);
    return contact;
  }
  
  @RequestMapping(value = "/contact", method = RequestMethod.GET)
  public ModelAndView showForm(Model model) {
    model.addAttribute("contact", new Contact());
    return new ModelAndView("contact");
  }
  
  @RequestMapping(value = "/add-contact", method = RequestMethod.POST)
  public ModelAndView onSubmit(HttpServletRequest request,
                @ModelAttribute("contact") @Validated Contact contact,
                BindingResult bindingResult, Model model) 
  {
    // form validation failed
    if (bindingResult.hasErrors()) {
      return new ModelAndView("contact");
    }
    
    // form validation passed
    // TODO: you can do anything you want here
    // for example: save contact data into database, sendmail, etc.
    
    // we also need to remove "captchaVerified" in session in order to show 
    // captcha for another request
    HttpSession session = request.getSession();
    session.removeAttribute("captchaVerified");
    
    return new ModelAndView("message", "contact", contact);
  }
    
}

There is no BotDetect-specific code in the index page controller, since all Captcha verification is done through a ContactValidator. 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.

Contact.java

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

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

public class Contact {
  
  private String name;
  private String email;
  private String subject;
  private String message;
  
  private HttpServletRequest httpRequest;
  
  private String captchaCode;

  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 getSubject() {
    return subject;
  }

  public void setSubject(String subject) {
    this.subject = subject;
  }

  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 getHttpRequest() {
    return httpRequest;
  }

  public void setHttpRequest(HttpServletRequest httpRequest) {
    this.httpRequest = httpRequest;
  }

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

The Contact model for contact form.

ContactValidator.java

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

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

public class ContactValidator implements Validator {
  
  @Override
  public boolean supports(Class<?> paramClass) {
    return Contact.class.equals(paramClass);
  }

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

    if (!isValidEmail(contact.getEmail())) {
      errors.rejectValue("email", "format", "*");
    }
    
    if (!isValidSubject(contact.getSubject())) {
      errors.rejectValue("subject", "length", "*");
    }

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

    if (!isCaptchaValid(contact.getHttpRequest(), contact.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 isValidSubject(String subject) {
    if (subject == null) {
      return false;
    }
    return ((subject.length() > 2) && (subject.length() < 50));
  }

  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.

mvc-dispatcher-servlet.xml

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans     
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context.xsd">

  <context:component-scan base-package="com.captcha.botdetect.examples.springmvc.form.controller" />
    
  <bean   
    class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix">
      <value>/WEB-INF/pages/</value>
    </property>
    <property name="suffix">
      <value>.jsp</value>
    </property>
  </bean>
  
  <bean 
    id="contactValidator" 
    class="com.captcha.botdetect.examples.springmvc.form.validator.ContactValidator"
  />
  
</beans>

We declare a Bean for ContactValidator.

messages.jsp

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>BotDetect Java CAPTCHA Validation: Spring MVC 5 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 5 Form CAPTCHA Code Example</h1>
    <br>
    <div class="message">
      <h3>Your contact was sent successfully!</h3>
      <p>Your name: ${contact.name}</p>
      <p>Your email ${contact.email}</p>
      <p>Your subject ${contact.subject}</p>
      <p>Your message: ${contact.message}</p>
    </div>
    
    <a class="backlink" href="contact"> << Back to Contact page.</a>
  </div>
</body>
</html>

This is just a success notification page, it also displays the submitted data.

MessageController.java

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

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class MessageController {
  
  @RequestMapping(value = "/message", method = RequestMethod.GET)
  public ModelAndView showForm(Model model) {
    return new ModelAndView("message");
  }
  
}

Controller for message.jsp.

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 xmlns="http://xmlns.jcp.org/xml/ns/javaee"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
     http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
     version="3.1">

  <servlet>
    <servlet-name>mvc-dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>mvc-dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  
  <servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.css</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>
  
  <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.