JavaServer Pages Login Form CAPTCHA Code Example

The JSP Login Form Captcha code example shows how to add BotDetect CAPTCHA protection to a typical JSP login form.

First Time Here?

Check the BotDetect JSP Captcha Quickstart for key integration steps.

To prevent bots from trying to guess the login info by brute force submission of a large number of common values, the visitor first has to prove they are human (by solving the CAPTCHA), and only then is their username and password submission checked against the authentication data store.

Also, if they enter an invalid username + password combination three times, they have to solve the CAPTCHA again. This prevents attempts in which the attacker would first solve the CAPTCHA themselves, and then let a bot brute-force the authentication info.

To keep the example code simple, the example doesn't access a data store to authenticate the user, but accepts all logins with usernames and passwords at least 5 characters long as valid.

Downloaded Location

The JSP Login Form CAPTCHA Example is included in examples folder of the download package as bdc3-jsp-login-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="botDetect" uri="botDetect"%>
<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Login Form CAPTCHA example</title>
    <link rel="stylesheet" href="stylesheet.css" type="text/css"/>
  </head>
  <body>
    <form action="loginFormAction" method="post">
      <h1>Login Form CAPTCHA example</h1>
      <fieldset>
        <legend>CAPTCHA included in Login form validation</legend>
        <table>
          <tr><td>User name:</td></tr>
          <% if(request.getAttribute("loginOk") != null){ %>
            <tr><td><input name="userId" type="text" 
                value="${param.userId}"/></td></tr>
          <% } else { %>
            <tr><td><input name="userId" type="text" /></td></tr>
          <% } %>

          <tr><td>Password:</td></tr>
          <% if(request.getAttribute("loginOk") != null){ %>
            <tr><td><input name="userPass" type="text" 
                value="${param.userPass}" /></td></tr>
          <% } else { %>
            <tr><td><input name="userPass" type="password" /></td></tr>
          <% } %>

          <% if(request.getAttribute("hideCaptcha") == null) { %>
          <tr>
            <td>
              <label for="captchaCodeTextBox">
                  Retype the characters from the picture:</label>
              <!-- Adding BotDetect Captcha to the page -->
              <botDetect:captcha
                id="loginFormCaptcha"
                codeLength="4"
                codeStyle="alpha"
              />
              <div class="validationDiv">
                <input id="captchaCodeTextBox" type="text" 
                    name="captchaCodeTextBox" />
              </div>
            </td>
          </tr>
          <% } %>
          <tr>
            <td>
              <% if(request.getAttribute("loginOk") == null){ %>
                <input type="submit" name="submit" value="Submit" />
                <span class="incorrect">${messages.loginErr}</span>
              <% } else { %>
                <a href="index.jsp" type="">Back to login page.</a>
              <% } %>
            </td>
          </tr>
        </table>
      </fieldset>
    </form>
  </body>
</html>

The example login form is simple, containing textboxes for username and password input. We add Captcha protection to the form using the standard procedure.

Note that we check for request parameter hideCaptcha to only display Captcha protection if it hasn't already been solved.

LoginFormAction.java

package botdetect.examples.jsp.login_form;

import botdetect.web.Captcha;
import java.io.IOException;
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;

public class LoginFormAction extends HttpServlet {
  private static final int MAX_FAILS = 3;
  private int validation, attempts;

  @Override
  protected void doPost(HttpServletRequest request, 
      HttpServletResponse response)
      throws ServletException, IOException {
    Map<String, String> messages = new HashMap<String, String>();
    request.setAttribute("messages", messages);
    if(request.getParameter("submit") != null){
      HttpSession session = request.getSession(true);

      if(session.getAttribute("LS_validation") != null){
        validation = (Integer)session.getAttribute("LS_validation");
      }
      if(session.getAttribute("LS_attempts") != null){
        attempts = (Integer)session.getAttribute("LS_attempts");
      }

      if(validation == 2){
        validation = 0;
        attempts = 0;
      }

      if(validation == 0){
        if(validateCaptcha(request)){
          validation = 1;
        } else {
          messages.put("loginErr", "Invalid CAPTCHA code!");
        }
      }

      if(validation == 1){
        if(validateLogin(request)){
          if(authenticate(request)){
            validation = 2;
            request.setAttribute("loginOk", true);
          } else {
            messages.put("loginErr", "Authentication failed");
            if(++attempts >= MAX_FAILS){
              attempts = 0;
              validation = 0;
            }
          }
        } else {
          messages.put("loginErr", "Invalid authentication info");
        }
      }
      if(validation > 0){
        request.setAttribute("hideCaptcha", true);
      }
      session.setAttribute("LS_validation", validation);
      session.setAttribute("LS_attempts", attempts);
    }
    RequestDispatcher dispatcher = 
        getServletContext().getRequestDispatcher("/index.jsp");
    dispatcher.forward(request, response);
  }

  private boolean validateCaptcha(HttpServletRequest request){
    // validate the Captcha to check we're not dealing with a bot
    Captcha captcha = Captcha.load(request, "loginFormCaptcha");
    boolean isHuman = captcha.validate(request, 
        request.getParameter("captchaCodeTextBox"));
    return isHuman;
  }

  private boolean validateLogin(HttpServletRequest request){
    String pattern = "^[a-zA-Z0-9_]+$"; 
    String userId = (String)request.getParameter("userId");
    String userPass = (String)request.getParameter("userPass");
    if(userId != null && userPass != null){
       return userId.matches(pattern) && userPass.matches(pattern);
    } else {
      return false;
    }
  }

  private boolean authenticate(HttpServletRequest request){
    return ((String)request.getParameter("userId")).length() > 4 &&
        ((String)request.getParameter("userPass")).length() > 4;
  }

}

We need to include the BotDetect Captcha library, and create a Captcha object with the same id ("loginFormCaptcha") and userInputClientId (default value: "captchaCodeTextBox") values as were used to show the Captcha on the login form.

Since we want to reset the Captcha status after 3 failed authentications, we keep the related counter in the LS_attempts session value.

This allows us to implement the proper Login form workflow:

  • The user must first solve the Captcha to prove they are human. This keeps the bots away from the authentication code, both conserving its resources and improving its security (since usernames and passwords will not be forwarded to the underlying data store if the Captcha is not solved first).
  • When the user has proven they are human, they get 3 authentication attempts without new Captcha tests, which allows them to remember the right combination in most cases.
  • If the user fails three authentication requests, they are shown a new Captcha which they must solve before continuing. This throttles authentication access, ensuring username + password combinations cannot be brute-forced, while real human users get theoretically unlimited authentication attempts (as long as they don't mind solving further Captchas).

As previously mentioned, the actual authentication code is not implemented, but the example code should demonstrate the underlying principles adequately.

After successful authentication, the example just displays the user input (while a real login form would set an auth cookie and redirect the user to actual functionality).

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.