Home > How to write a simple CAPTCHA in Java

How to write a simple CAPTCHA in Java
Posted by Kaleb Brasee on Sunday, April 12th, 2009 at 12:16 AM
When I opened up my site earlier this week, I was surprised to find over 300 replies on one post. "Wow", I thought to myself, "I am now a powerful force in the blog-o-sphere." Turns out, some stupid spambots kept selling dipophedrin and tranzambol and eptexerdoze on my site every 5 minutes. I tried changing the URL used to post comments but the bots were far too smart for that feeble attempt at a fix, and they were posting comments again 10 minutes after I put up the new build.

So I decided it was time to add CAPTCHA (Completely Automated Public Turing test to tell Computers and Humans Apart) validation. CAPTCHA provides a way to block bots from interacting with your site by providing something that's hard for them to read, but easy for people to read. I wanted to try something simple that I could write myself to see how it works. Read on to see how I implemented simple CAPTCHA in my Java web app.



1. Generate a random string and save it to the user's session
Whenever a page is loaded that needs to use a CAPTCHA, have the controller call a method that creates a new random string and save it in the user's session. I do the randomization using Java's SecureRandom class and the saving using Spring's WebUtils.getSessionAttribute and setSessionAttribute methods.

For anyone interested, here's the code to generate the random string:
/**
 *  Generate a CAPTCHA String consisting of random lowercase & uppercase letters, and numbers.
 */
public String generateCaptchaString() {
	int length = 7 + (Math.abs(random.nextInt()) % 3);

	StringBuffer captchaStringBuffer = new StringBuffer();
	for (int i = 0; i < length; i++) {
		int baseCharNumber = Math.abs(random.nextInt()) % 62;
		int charNumber = 0;
		if (baseCharNumber < 26) {
			charNumber = 65 + baseCharNumber;
		}
		else if (baseCharNumber < 52){
			charNumber = 97 + (baseCharNumber - 26);
		}
		else {
			charNumber = 48 + (baseCharNumber - 52);
		}
		captchaStringBuffer.append((char)charNumber);
	}

	return captchaStringBuffer.toString();
}


2. Write a servlet to generate the CAPTCHA image using the string
I haven't used the Java graphics API very much before, but some quick googling led me to this good example on RoseIndia.net. It uses the Java 2D library to generate a JPEG using a string, so I pass in the CAPTCHA string that is associated with the user's session. Visit that page for all the code you need to generate such an image.

3. Invoke the servlet to display the CAPTCHA image, and make the user type it in
This is simple, just have the page that uses CAPTCHA validation display an HTML <img .../> with the src referring to your CAPTCHA servlet. For example, '<img src="displayCaptcha.htm" alt="CAPTCHA image" />'. Provide a text field for the user to type in the letters & number that they see in the image.

4. Check for a match between the user's input and session CAPTCHA value
Upon form submission, compare what the user typed in for the CAPTCHA with the real value that's stored in their session. Here's the code I used to validate their input:
UserSession userSession = (UserSession) WebUtils.getSessionAttribute(request, "userSession");
if (postCommand.getCaptcha() == null || userSession == null
		|| !postCommand.getCaptcha().equalsIgnoreCase(userSession.getCaptchaString())) {
	errors.rejectValue("captcha", "", "Error: CAPTCHA is incorrect.");
}
If these values match, allow the form submission to continue. If the values don't match, then REJECTED! Ignore their input and display an error telling them that their input was invalid. In my application, I reload the page so that the CAPTCHA image is regenerated.



So that is a basic overview of what I've done to bot-proof my Java site using simple CAPTCHA. After I put up the new build, the bots tried to post 3 more comments and were subsequently rejected, so it looks like my approach is good enough to stop them. If they ever get too smart and find a way to analyze the image, I guess I'll start working on a better image generator!