reCAPTCHA and Spring MVC integration

Why use reCAPTCHA?

We have a section on Podcastpedia.org, called Recommend podcast, that allows visitors to submit podcasts. Lately we have received very good suggestions – thank you all you Guys for that, but also lots of spam. A way to signifactly reduce the amount of spam, is to use captchas, which is a type of challenge-response test used in computing to determine whether or not the user is human. One popular implementaiton of captchas, is reCAPTCHA, now owned by Google. You might thing that solving catpchas is annoying, but by using reCAPTCHA you help to digitize books, newspapers and old time radio shows – here is a greatd TED talk from Luis von Ahn on massive-scale online collaboration explaining how this works:

Last but not least, you help us avoid email spam, which I guess you know by now how annoying and dangerous that can be.

This post presents how reCAPTCHA is integrated with a Spring MVC form to recommend podcasts.

Octocat Source code for this post is available on Github - podcastpedia.org is an open source project.

Sign up for a reCAPTCHA account

Before using reCAPTCHA you have to sign up for an account. For your domain, you will receive then a Public Key, to use in the JavaScript code that is served to your users and a Private Key to use when communicating between your server and reCAPTCHA server – make sure to keep the latter secret.

reCAPTCHA Spring MVC integration

Maven dependency

Add the following dependency to your pom.xml file

<!-- reCaptcha -->
<dependency>
	<groupId>net.tanesha.recaptcha4j</groupId>
	<artifactId>recaptcha4j</artifactId>
	<version>0.0.7</version>
</dependency>

The current Spring version used for Podcastpedia is <spring.version>3.2.3.RELEASE</spring.version>

If you don’t use Maven to build your application you can download the reCAPTCHA Java Library here (contributed by Soren) and unzip it. Typically the only thing you’ll need is the jar file (recaptcha4j-X.X.X.jar), which you have to copy to a place where it can be loaded by your java application. For example, if you are using Tomcat to run JSP, you may put the jar file in a directory called WEB-INF/lib/.

Client Side (How to make the CAPTCHA image show up)

To use the Java plugin to display the reCAPTCHA widget, you’ll need to import the appropriate reCAPTCHA classes. In JSP, you would do this by inserting these lines near the top of the file with the form element where the reCAPTCHA widget will be displayed:

<%@ page import="net.tanesha.recaptcha.ReCaptcha" %>
<%@ page import="net.tanesha.recaptcha.ReCaptchaFactory" %>

Then, you need to create an instance of reCAPTCHA:

ReCaptcha c = ReCaptchaFactory.newReCaptcha("your_public_key", "your_private_key", false);

Finally, the HTML to display the reCAPTCHA widget can be obtained from the following function call:

c.createRecaptchaHtml(null, null)

I wanted the reCAPTCHA widget customized, so I used the “clean” theme and also introduced support to internationalize the reCAPTCHA snippet with my own messages. For that the following JavaScript code had to be included in the jsp file:

<script type="text/javascript">
	var strings = new Array();
	strings['recaptcha.instructions_visual'] = "<spring:message code='recaptcha.instructions_visual' javaScriptEscape='true'/>";
	strings['recaptcha.instructions_audio'] = "<spring:message code='recaptcha.instructions_audio' javaScriptEscape='true'/>";
	strings['recaptcha.play_again'] = "<spring:message code='recaptcha.play_again' javaScriptEscape='true'/>";
	strings['recaptcha.cant_hear_this'] = "<spring:message code='recaptcha.cant_hear_this' javaScriptEscape='true'/>";
	strings['recaptcha.visual_challenge'] = "<spring:message code='recaptcha.visual_challenge' javaScriptEscape='true'/>";
	strings['recaptcha.audio_challenge'] = "<spring:message code='recaptcha.audio_challenge' javaScriptEscape='true'/>";
	strings['recaptcha.refresh_btn'] = "<spring:message code='recaptcha.refresh_btn' javaScriptEscape='true'/>";
	strings['recaptcha.help_btn'] = "<spring:message code='recaptcha.help_btn' javaScriptEscape='true'/>";
	strings['recaptcha.incorrect_try_again'] = "<spring:message code='recaptcha.incorrect_try_again' javaScriptEscape='true'/>";

	var RecaptchaOptions = {
	custom_translations : {
		 instructions_visual :  strings['recaptcha.instructions_visual'] ,
		 instructions_audio : strings['recaptcha.instructions_audio'],
		 play_again : strings['recaptcha.play_again'],
		 cant_hear_this : strings['recaptcha.cant_hear_this'],
		 visual_challenge : strings['recaptcha.visual_challenge'],
		 audio_challenge : strings['recaptcha.audio_challenge'],
		 refresh_btn : strings['recaptcha.refresh_btn'],
		 help_btn : strings['recaptcha.help_btn'],
		 incorrect_try_again : strings['recaptcha.incorrect_try_again']
	},
	theme : 'clean'
	};
</script>

The strings array contains the values of the localized messages I want displayed in the captcha widget. See the post Spring 3 MVC: Internationalization & localization of Podcastpedia.org to find out how internationalization and localization is achieved for Podcastpedia.org

If put together, all these relevant things look somewhat like the following in the jsp file:

<?xml version="1.0" encoding="UTF-8"?>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ page import="net.tanesha.recaptcha.ReCaptcha" %>
<%@ page import="net.tanesha.recaptcha.ReCaptchaFactory" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%@ include file="/WEB-INF/jsp/common/recaptcha_options.jsp" %>

.....
	<form:form id="add_podcast_form" class="vertical_style_form"
		method="POST" modelAttribute="addPodcastForm">
		.....
		<div id="captcha_paragraph">
			<c:if test="${invalidRecaptcha == true}">
				<span class="error_form_validation"><spring:message code="invalid.captcha" text="Invalid captcha please try again"/></span>
			</c:if>
		    <%
		        ReCaptcha c = ReCaptchaFactory.newReCaptcha("6LcW3OASAAAAAKEJTHMmp_bo5kny4lZXeDtgcMqC",
		        					"6LcW3OASAAAAAKVX2duVsSy2uMMHL105-jPDrHMD", false);
		        out.print(c.createRecaptchaHtml(null, null));
		    %>
		</div>
		.....
	</form:form>
	<span style="color:#A73030;margin-left:30px;font-weight: bold;">*<spring:message code="required" text="required"/></span>
</div>

where /WEB-INF/jsp/common/recaptcha_options.jsp contains the customization options mentioned above. The result looks similar to the following:

recaptcha widget

Server Side (How to test if the user entered the right answer)

First the reCAPTCHA bean has to be configured in the Spring application context:

<?xml version="1.0" encoding="UTF-8"?>
<beans  xmlns="http://www.springframework.org/schema/beans"
	    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	    xmlns:jaxws="http://cxf.apache.org/jaxws"
	    xmlns:p="http://www.springframework.org/schema/p"
	    xmlns:context="http://www.springframework.org/schema/context"
	    xmlns:tx="http://www.springframework.org/schema/tx"
	    xmlns:mvc="http://www.springframework.org/schema/mvc"
	    xmlns:cache="http://www.springframework.org/schema/cache"
	    xsi:schemaLocation="
			http://www.springframework.org/schema/beans
			http://www.springframework.org/schema/beans/spring-beans.xsd
			http://www.springframework.org/schema/mvc
			http://www.springframework.org/schema/mvc/spring-mvc.xsd
			http://www.springframework.org/schema/context
			http://www.springframework.org/schema/context/spring-context.xsd
			http://cxf.apache.org/jaxws
			http://cxf.apache.org/schemas/jaxws.xsd
			http://www.springframework.org/schema/tx
			http://www.springframework.org/schema/tx/spring-tx.xsd
			http://www.springframework.org/schema/cache
			http://www.springframework.org/schema/cache/spring-cache.xsd">

	<context:component-scan base-package="org.podcastpedia.service, org.podcastpedia.controllers"/>
	<mvc:annotation-driven/>
	......
	<bean id="reCaptcha" class="net.tanesha.recaptcha.ReCaptchaImpl">
		<property name="privateKey" value="your_recaptcha_private_key"/>
	</bean>
	......
</beans>

The captcha verification occurs directly in the controller:

package org.podcastpedia.controllers;

import net.tanesha.recaptcha.ReCaptchaImpl;
import net.tanesha.recaptcha.ReCaptchaResponse;
....

@Controller
@RequestMapping("/how_can_i_help")
@SessionAttributes("addPodcastForm")
public class HowCanIhelpController {
	@Autowired
	ReCaptchaImpl reCaptcha;
	.....
	@RequestMapping(value="add_podcast", method=RequestMethod.POST)
	public String processAddPodcastForm(
			@ModelAttribute("addPodcastForm") SuggestedPodcast addPodcastFormData,
			BindingResult result,
			Model model,
			@RequestParam("recaptcha_challenge_field") String challangeField,
			@RequestParam("recaptcha_response_field") String responseField,
			ServletRequest servletRequest,
			SessionStatus sessionStatus){

		LOG.debug("------ processAddPodcastForm : form is being validated and processed -----");
		suggestPodcastValidator.validate(addPodcastFormData, result);

		String remoteAddress = servletRequest.getRemoteAddr();
		ReCaptchaResponse reCaptchaResponse = this.reCaptcha.checkAnswer(remoteAddress, challangeField, responseField);

		if(reCaptchaResponse.isValid() && !result.hasErrors()){

        	userInteractionService.addSuggestedPodcast(addPodcastFormData);
        	emailNotificationService.sendSuggestPodcastNotification(addPodcastFormData);
        	sessionStatus.setComplete();

        	return "redirect:/how_can_i_help/add_podcast?tks=true";
		} else {
			model.addAttribute("addPodcastForm", addPodcastFormData);
    		if(!reCaptchaResponse.isValid()) {
    			result.rejectValue("invalidRecaptcha", "invalid.captcha");
    			model.addAttribute("invalidRecaptcha", true);
    		}
			return "add_podcast_form_def";
		}

	}
	.....
}

First you need to import the necessary classes (lines 3 and 4), autowire the reCaptcha bean defined in the application context (line 12), initialize the passed captcha parameter fields (lines 19 and 20) and validate the recaptcha challenge and response (line 28).

The remoteAddress (line 27) is the user’s IP address or last proxy, which is passed to the reCAPTCHA servers.

If reCAPTCHA response is valid and all the other form fields are properly filled (line 30) a notification email is sent with the recommended podcast and the current handler’s session processing is marked as complete, allowing for cleanup of session attributes. If there are errors corresponding error messages are displayed.

Well, that’s it. After reading this I hope you feel more positive about resolving captchas and integrating them on your website.

10x

  • Many thanks to the guys who created captcha/reCaptcha
  • very much to the guys who recommended podcasts for the directory and remember if you know any good podcast worth sharing tell us about it and we will add it to the directory.

Octocat Source code for this post is available on Github - podcastpedia.org is an open source project.

References

  1. reCAPTCHA
  2. Using reCAPTCHA with JAVA/JSP
  3. Customizing the Look and Feel of reCAPTCHA
  4. Spring Framework Reference Documentation
Podcastpedia image

Adrian Matei

Creator of Podcastpedia.org and Codingpedia.org, computer science engineer, husband, father, curious and passionate about science, computers, software, education, economics, social equity, philosophy - but these are just outside labels and not that important, deep inside we are all just consciousness, right?

Adrian’s favorite Spring books

How to configure Nginx in production to serve an Angular app and reverse proxy NodeJS

Install Nginx on Ubuntu Server, understand configuration files, configure SSL, serve static files, reverse proxy Keycloak and NodeJS servers Continue reading