Blaskowitz Blaskowitz - 20 days ago 15
Ajax Question

Implement Antibot Captcha with Java Spring MVC using Thymeleaf and JQuery AJAX

I'm trying to implement Captcha with my Spring MVC application. Although there are some examples of how to do that, I couldn't find any where the form is submitted with JQuery AJAX and the template engine is Thymeleaf.

My main source of information is this.

I've added the dependencies and the servlet, but I'm struggling with the template part (where I'm using Thymeleaf and JQuery AJAX to send my form to the controller) and to do the validation in the controller.

I don't necessarily want to use Captcha as my antibot framework, so if you have any ideas using other framework, I would be more than glad to hear them.

Answer

Ok, so I ended up using Cage Captcha generator. It can be integrated with Maven and it's fairly easy to be implemented with a Spring MVC application with JQuery AJAX.

/**
 * Generates captcha as image and returns the image path
 * stores the captcha code in the http session
 * and deletes older, unused captcha images.
 */
@RequestMapping(value = "/captcha/generate", method = RequestMethod.GET, produces="application/json")
@ResponseBody
public ResponseEntity<CaptchaRequestData> generateCaptcha(HttpSession session) {
    String captchaImageUploadDirectory = environment.getProperty("captcha_image_folder");
    String captchaWebAlias = environment.getProperty("captcha_web_alias");

    //Creating dir or making new one if it doesn't exist
    File file = new File(captchaImageUploadDirectory);
    if (!file.exists()) {
        try {
            file.mkdirs();
        } catch(Exception e){}
    }

    String timeSuffix = DBUtils.getDateTimeAsString();
    String fileName = CAPTCHA_IMAGE_PREFIX + timeSuffix + "." + CAPTCHA_IMAGE_EXTENSION;
    String fullFilename = captchaImageUploadDirectory + fileName;

    //Generating the captcha code and setting max length to 4 symbols
    Cage currGcage = new YCage();
    String captchaToken = currGcage.getTokenGenerator().next();

    if (captchaToken.length() > CAPTCHA_CODE_MAX_LENGTH) {
        captchaToken = captchaToken.substring(0, CAPTCHA_CODE_MAX_LENGTH).toUpperCase();
    }

    //Setting the captcha token in http session
    session.setAttribute("captchaToken", captchaToken);

    try {
        OutputStream os = new FileOutputStream(fullFilename, false);
        currGcage.draw(captchaToken, os);
        os.flush();
        os.close();
    } catch (IOException e) {
        e.printStackTrace();
    }

    deleteFilesOlderThan(captchaImageUploadDirectory, CAPTCHA_IMAGE_LIFE_MILLISECONDS, CAPTCHA_IMAGE_EXTENSION);

    CaptchaRequestData data = new CaptchaRequestData(captchaWebAlias + fileName);

    return new ResponseEntity<>(data, HttpStatus.OK);
}

Then when I'm creating the object I check if the given code equals the one, stored in the session:

 if (!httpSession.getAttribute("captchaToken").equals(bindingData.getCaptchaCode())) {
        return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
 }

Finally if the provided captcha is incorrect I generate a new one.