Generating a CAPTCHA in a Headless Java™ Environment
Google the word "CAPTCHA" and the top entry is from Wikipedia. The Wikipedia defines CAPTCHA (Completely Automated Public Turing test to tell Computers and Humans Apart), which is trademarked by Carnegie Mellon University, as challenge-response test used distinguish a computers from a person. The basic idea is to create an image that is difficult for a computer to read and easy for a person to read. The image is randomly generated with a code. The code is known on the server and the code is known by the person looking at the image (Figure 1).
Figure 1: Generation of CAPTCHA image and code on the server and the interpretation of the code by the person.
Using the Java's awt it's relatively easy to paint a random CAPTCHA image and serve the image up via a Java servlet. There's a good implementation you can look at called simplecaptcha, which is hosted on sourceforge. Depending on where your servlet is hosted the awt may or may not be available. If your CAPTCHA is not working you may need to:
- ensure an X-server is available.
- start your server with a
-Djava.awt.headless=truevm switch - start the framebuffer server:
/usr/X11R6/bin/Xvfb :0 -screen 0 1024x768x24 - give permission to the local machine:
/usr/X11R6/bin/xhost +localhost
If you don't have permission to do that, then you need to try some alternatives. One alternative is to use a PHP page and the code you can freely use from the people at White Hat Web Design. If you are able to use PHP pages on your server and want to use that code in your servlet, then you have a problem because the PHP session with not be available in your servlet; however, there is a solution (Figure 2).
Figure 2: PHP page streams image plus code to servlet and servlet decodes image and code.
With a slight modification to the White Hat Web Design's code you can append the security code into the stream. The servlet can then separate the image from the security code and store the code in Java's session. If you had a simplecaptcha working and it no longer works on your server, then swap in the servlet shown in Listing 1.
Listing 1: Java servlet to read the PHP CAPTCHA image plus security code stream.
package ca.ansir.servlet;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* The <code>Captcha</code> class streams a PHP generated CAPTCHA image with 6
* characters. The characters are visible in the image and also appended to the
* image stream. The <code>doGet</code> method streams only the image and
* saves the code as a session attribute. The PHP page for a CAPTCHA image with
* the appended 6 characters has been hard coded and you will want to use your
* own PHP page. We will not make guarantees as to the up time of our PHP
* CAPTCHA page. If you are in a bind and can't host PHP pages then you may use
* ours, but in that case we ask that you kindly provide a link back to our
* site.
* <p>
* Please feel free to use this code snippet (<code>Captcha</code>) in
* developing your own commercial or non-commercial applications, but please
* credit the author, Dan Andrews, for any portion of this code that you use,
* and provide a reference to <a href="http://www.ansir.ca">Ansir</a>. Thank
* you.
* </p>
*
* @author Dan Andrews
*/
public class Captcha extends HttpServlet implements Servlet {
/** Session key. */
public static final String SIMPLE_CAPCHA_SESSION_KEY = "SIMPLE_CAPCHA_SESSION_KEY";
/** Code length. */
private static final int CODE_LENGTH = 6;
/** Change this to your CaptchaSecurityImages.php location. */
private static final String PHP_CAPTHCA = "http://www.ansir.ca/images/CaptchaSecurityImages.php";
/**
* Constructor
*/
public Captcha() {
}
/**
* Handles a GET request.
*
* @param req
* The request object.
* @param res
* The response object.
*/
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
byte[] bytes = getCaptchaBytes();
String strCode = getCaptchaCode(bytes);
res.setContentType("image/jpeg");
req.getSession().setAttribute(SIMPLE_CAPCHA_SESSION_KEY, strCode);
writeImage(bytes, res.getOutputStream());
}
/**
* Gets the security code, which was appended by the PHP page.
*
* @param bytes
* All the CAPTHCA bytes (image plus code).
* @return The CAPTHCA security code to store in the session.
*/
private String getCaptchaCode(byte[] bytes) {
String strCode;
byte[] code = new byte[CODE_LENGTH];
for (int i = 0, index = bytes.length - CODE_LENGTH; i < CODE_LENGTH; i++) {
code[i] = bytes[index + i];
}
strCode = new String(code);
return strCode;
}
/**
* Gets all the CAPTHCA bytes (image plus code).
*
* @return All the CAPTHCA bytes (image plus code).
* @throws MalformedURLException
* @throws IOException
*/
private byte[] getCaptchaBytes() throws MalformedURLException, IOException {
URL url = new URL(PHP_CAPTHCA);
URLConnection connection = url.openConnection();
InputStream in = connection.getInputStream();
byte[] bytes = new byte[1024];
int read = -1;
ByteArrayOutputStream out = new ByteArrayOutputStream();
while ((read = in.read(bytes)) > 0) {
out.write(bytes, 0, read);
}
bytes = out.toByteArray();
return bytes;
}
/**
* Writes the image to the given stream.
*
* @param bytes
* All the CAPTHCA bytes (image plus code).
* @param out
* The <code>OutputStream</code> to write the image to.
* @throws IOException
*/
private void writeImage(byte[] bytes, OutputStream out) throws IOException {
out.write(bytes, 0, bytes.length - CODE_LENGTH);
}
/**
* Test only.
*
* @param args
* Not used.
*/
public static void main(String[] args) throws MalformedURLException,
IOException {
Captcha captcha = new Captcha();
byte[] bytes = captcha.getCaptchaBytes();
String strCode = captcha.getCaptchaCode(bytes);
System.out.println("Raw Captcha Image");
captcha.writeImage(bytes, System.out);
System.out.print("\nImage Code = ");
System.out.println(strCode);
}
}
We can not guarantee our PHP CAPTCHA page and we would prefer you use your own page. If
you have no other viable alternatives, then use ours but kindly provide a link back to our
site from your web application. To use your own PHP page you begin by downloading White Hat Web Design's code. Modify the
CaptchaSecurityImages.php file as shown in Listing 2. Copy the modified file and the
monofont.ttf to an appropriate location on your server. Next test the modified code by
opening up the image in a browser (e.g.
http://www.ansir.ca/images/CaptchaSecurityImages.php). In our case Internet Explorer will
not save the image as a JPG, so we open it in Firefox. Save the JPG and open in a text
editor (e.g notepad will work). Verify that the last six characters are at the end of the
modified image stream.
Listing 2: Modified PHP CAPTCHA to append the security code to the image stream.
<?php
session_start();
/*
* File: CaptchaSecurityImages.php
* Author: Simon Jarvis
* Copyright: 2006 Simon Jarvis
* Date: 03/08/06
* Link: http://www.white-hat-web-design.co.uk/articles/php-captcha.php
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details:
* http://www.gnu.org/licenses/gpl.html
*
*/
class CaptchaSecurityImages {
var $font = 'monofont.ttf';
function generateCode($characters) {
/* list all possible characters, similar looking characters have been removed */
$possible = '23456789ABCDE';
$code = '';
$i = 0;
while ($i < $characters) {
$code .= substr($possible, mt_rand(0, strlen($possible)-1), 1);
$i++;
}
return $code;
}
function CaptchaSecurityImages($width='120',$height='40',$characters='6') {
$code = $this->generateCode($characters);
/* font size will be 75% of the image height */
$font_size = $height * 0.75;
$image = @imagecreate($width, $height) or die('Cannot Initialize new GD image stream');
/* set the colours */
$background_color = imagecolorallocate($image, 255, 255, 255);
$text_color = imagecolorallocate($image, 20, 40, 100);
$noise_color = imagecolorallocate($image, 100, 120, 180);
/* generate random dots in background */
for( $i=0; $i<($width*$height)/3; $i++ ) {
imagefilledellipse($image, mt_rand(0,$width), mt_rand(0,$height), 1, 1, $noise_color);
}
/* generate random lines in background */
for( $i=0; $i<($width*$height)/150; $i++ ) {
imageline($image, mt_rand(0,$width), mt_rand(0,$height), mt_rand(0,$width), mt_rand(0,$height), $noise_color);
}
/* create textbox and add text */
$textbox = imagettfbbox($font_size, 0, $this->font, $code);
$x = ($width - $textbox[4])/2;
$y = ($height - $textbox[5])/2;
imagettftext($image, $font_size, 0, $x, $y, $text_color, $this->font , $code);
/* output captcha image to browser */
imagejpeg($image);
imagedestroy($image);
/* Modified: Print the code at the end of the image and comment out session */
print($code);
/*$_SESSION['security_code'] = $code;*/
}
}
$width = isset($_GET['width']) ? $_GET['width'] : '120';
$height = isset($_GET['height']) ? $_GET['height'] : '40';
$characters = isset($_GET['characters']) ? $_GET['characters'] : '6';
header('Content-Type: image/jpeg');
$captcha = new CaptchaSecurityImages($width,$height,$characters);
?>
Next copy the servlet to your web application. Since it has a main method you can run
it directly for another test and it will print a string representation of the image and
the 6 security code characters. Now configure your web application for the servlet as
shown in Listing 3. Finally test your servlet and integrate it in your web application.
Listing 3: Web Application configuration.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>DisplayName</display-name>
<description>Your Web Application</description>
<!-- Define Captcha servlet -->
<servlet>
<servlet-name>Captcha</servlet-name>
<servlet-class>ca.ansir.servlet.Captcha</servlet-class>
</servlet>
<!-- Define other servlets that are included in the this application -->
...
<servlet-mapping>
<servlet-name>Captcha</servlet-name>
<url-pattern>/captcha.jpg</url-pattern>
</servlet-mapping>
<!-- Define other part of this application -->
...
</web-app>

