BotDetect CAPTCHA Options: Request Dynamic Settings Code Example

The Java Captcha options: Request dynamic settings code example shows how to dynamically adjust Captcha configuration, potentially on each Http request made by the client.

First Time Here?

Check the BotDetect Developer Crash Course for key integration steps.

Any Java code setting Captcha properties in the CaptchaFilter.java file will be executed not only for each protected form GET or POST request (like Captcha configuration code placed in form source would be), but also for each GET request loading a Captcha image or sound, or making an Ajax Captcha validation call.

If configured values are dynamic (e.g. CaptchaRandomization helper or other function calls in CaptchaFilter.java code), they will be re-calculated for each Captcha challenge generated. For example, Captcha ImageStyle randomized in CaptchaFilter.java code will change on each Captcha reload button click.

This means your code can reliably keep track of visitor interaction with the Captcha challenge and dynamically adjust its settings. Also, while CaptchaFilter.java settings apply to all Captcha instances by default, you can also selectively apply them based on CaptchaId.

To show an example of the possible dynamic Captcha configuration adjustments, this code example increases the difficulty of the Captcha test if the visitor associated with the current Java Session fails a certain number of Captcha validation attempts, and also sets the Captcha locale to Chinese for requests from a certain IP range.

Download the BotDetect Java CAPTCHA Library and 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~conf_via-dynamic_config/ 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 and botdetect-servlet.jar are in the classpath.

index.jsp

<%@page import="com.captcha.botdetect.CodeStyle"%>
<%@page import="com.captcha.botdetect.examples.dynamic_captcha.Counter"%>
<%@page import="com.captcha.botdetect.web.servlet.Captcha"%>
<%@page trimDirectiveWhitespaces="true"%>
<%@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 Options: Request Dynamic Settings Code Example</title>
  <link type="text/css" rel="stylesheet" href="stylesheet.css" />
</head>
<body>
  <form method="post" action="" class="column" id="form1">
    <h1>BotDetect Java CAPTCHA Options: <br /> Request Dynamic Settings Code Example</h1>

    <fieldset>
      <legend>Java CAPTCHA validation</legend>
      <label for="captchaCode">Retype the characters from the picture:</label>
      <%
        // Adding BotDetect Captcha to the page
        Captcha captcha = Captcha.load(request, "dynamicCaptcha");
        captcha.setUserInputID("captchaCode");
        String captchaHtml = captcha.getHtml();
        out.write(captchaHtml);
      %>

      <div class="validationDiv">
        <input name="captchaCode" type="text" id="captchaCode" />
        <input type="submit" name="SubmitButton" id="SubmitButton" value="Submit Form" />
        
        <%
          Counter counter = new Counter(request.getSession());
          
          if ("POST".equalsIgnoreCase(request.getMethod())) {
            // validate the Captcha to check we're not dealing with a bot
            boolean isHuman = captcha.validate(request.getParameter("captchaCode"));
            if (!isHuman) {
              // Captcha validation failed, show error message
              out.print("<span class=\"incorrect\">Incorrect code</span>");
              counter.incrementFailedValidationsCount();
            } else {
              // Captcha validation passed, perform protected action
              out.print("<span class=\"correct\">Correct code</span>");
              counter.resetFailedValidationsCount();
            }
          }
        %>
      </div>
    </fieldset>

    <div id="output">
      <% 
        Integer count = counter.getFailedValidationsCount();
      %>

      <p>Failed Captcha validations: <%= count %></p>
      <%
        if (count < 3) {
          out.print("<p>Dynamic Captcha difficulty: Easy</p>");
        } else if (count < 10) {
          out.print("<p>Dynamic Captcha difficulty: Moderate</p>");
        } else {
          out.print("<p>Dynamic Captcha difficulty: Hard</p>");
        }
      %>
    </div>
  </form>
</body>
</html>

In the form source, we follow the standard procedure for adding Captcha protection to a JSP form.

On POST request, if Captcha validation fails, the increment of failed validation is executed by calling incrementFailedValidationsCount() method of counter object. Otherwise, it will be reset by calling the resetFailedValidationsCount() method of counter object.

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>
  
  <filter>
    <filter-name>captchaFilter</filter-name>
    <filter-class>com.captcha.botdetect.examples.dynamic_captcha.CaptchaFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>captchaFilter</filter-name>
    <servlet-name>BotDetect Captcha</servlet-name>
  </filter-mapping>
  
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

In WEB-INF/web.xml file we declare custom filter and map it to BotDetect Captcha servlet in order to dynamically adjust Captcha configuration for every Captcha generation request.

Counter.php

package com.captcha.botdetect.examples.dynamic_captcha;

import javax.servlet.http.HttpSession;

/**
 * Helper class for counting Captcha validation failures at form submission
 */
public class Counter {
  
  private HttpSession session;
  private static final String FAILED_VALIDATIONS_COUNT_KEY = "failedValidationsCount";
  
  public Counter(HttpSession session) {
    this.session = session;
  }
  
  public int getFailedValidationsCount() {
    Integer count = (Integer) session.getAttribute(FAILED_VALIDATIONS_COUNT_KEY);
    if (count == null) {
      count = 0;
    }
    
    return count.intValue();
  }
  
  public void incrementFailedValidationsCount() {
    int count = getFailedValidationsCount();
    count++;
    session.setAttribute(FAILED_VALIDATIONS_COUNT_KEY, count);
  }
  
  public void resetFailedValidationsCount() {
    session.removeAttribute(FAILED_VALIDATIONS_COUNT_KEY);
  }
}

The helper class for counting Captcha validation failures at form submission.

CaptchaFilter.java

package com.captcha.botdetect.examples.dynamic_captcha;

import com.captcha.botdetect.CaptchaRandomization;
import com.captcha.botdetect.CodeStyle;
import com.captcha.botdetect.web.CaptchaHttpCommand;
import com.captcha.botdetect.web.servlet.Captcha;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

public class CaptchaFilter implements Filter {

  @Override
  public void doFilter(ServletRequest request, ServletResponse response,
      FilterChain chain)
      throws IOException, ServletException {
    
    CaptchaHttpCommand command = CaptchaHttpCommand.getCaptchaCommand(request.getParameter("get"));
    
    if ((command == CaptchaHttpCommand.GET_IMAGE)
        || (command == CaptchaHttpCommand.GET_SOUND)
        || (command == CaptchaHttpCommand.GET_VALIDATION_RESULT)) {
      String captchaId = request.getParameter("c");
      Captcha captcha = Captcha.load(request, captchaId);
      
      // Dynamic Captcha settings depending on failed validation attempts: increase Captcha 
      // difficulty according to number of previously failed validations
      Counter counter = new Counter(((HttpServletRequest) request).getSession());
      Integer count = counter.getFailedValidationsCount();
      if (count < 3) {
        captcha.setCodeLength(CaptchaRandomization.getRandomCodeLength(3, 4));
        captcha.setCodeStyle(CodeStyle.NUMERIC);
        captcha.setCodeTimeout(600); // 10 minutes
      } else if (count < 10) {
        captcha.setCodeLength(CaptchaRandomization.getRandomCodeLength(4, 6));
        captcha.setCodeStyle(CodeStyle.ALPHA);
        captcha.setCodeTimeout(180); // 3 minutes
      } else {
        captcha.setCodeLength(CaptchaRandomization.getRandomCodeLength(6, 9));
        captcha.setCodeStyle(CodeStyle.ALPHANUMERIC);
        captcha.setCodeTimeout(60); // 1 minutes
      }
      
      // Set Captcha locale to Chinese for requests from a certain IP range
      String testIPRange = "223.254.";
      if (request.getRemoteAddr().startsWith(testIPRange)) {
        captcha.setCodeStyle(CodeStyle.ALPHA);
        captcha.setLocale("cmn");
      }

      // Save captcha settings
      captcha.save(captcha);
    }
    
    chain.doFilter(request, response);
  }

  @Override
  public void destroy() {
  }

  @Override
  public void init(FilterConfig filterConfig) {
  }
}

The dynamic Captcha settings will have various values according to number of previously failed validations. It also sets Captcha locale to Chinese for requests from a certain IP range (i.e. '223.254.' in code above) .


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.