Child pages
  • JCaptcha and the SpringFramework
Skip to end of metadata
Go to start of metadata

Introduction 

JCaptcha tries to strictly respect the Inversion of Control pattern, to ease creation of components for concrete applications.
On the other side the Spring Framework allows to use the power of IOC combined with XML declarations.
So that, every single component of jCaptcha can be declared in XML, and instanced through Spring.
An application using jCaptcha should only manipulate a CaptchaService instance.

5-second configuration

The simplest configuration possible is to declare the following in your Spring context

<bean class="com.octo.captcha.service.image.DefaultManageableImageCaptchaService" id="imageCaptchaService"/>

10-seconds configuration

Showing how to change the default engine, for getting diferent captchas:

 <bean id="captchaEngine" class="com.octo.captcha.engine.image.gimpy.SimpleListImageCaptchaEngine"/>
<bean id="captchaService" class="com.octo.captcha.service.image.DefaultManageableImageCaptchaService"
singleton="true">
<property name="captchaEngine" ref="captchaEngine"/>
</bean>

Now if you want to customize your engine configuration, you'll need to define your own implementation

Setting a captcha custom service and engine configuration in Spring

In this section we will describe how to set components to create a full CaptchaEngine.

We will start with what we want, the CaptchaEngine, for each component we provide a working and common example, but every component has several implementations, see the Java doc for more details. The construction of each component in XML must respect a constructor of this component.

There are several Engines pre-configured, but as we want to control configuration, we have to use the GenericCaptchaEngine, which is built with a list of captcha factories (factories are the ?real? producer of captchas)

<bean id="imageEngine" class="com.octo.captcha.engine.GenericCaptchaEngine">
	<constructor-arg index="0">
		<list>
			<ref bean="CaptchaFactory"/>
		</list>
	</constructor-arg>
</bean>

Then, a CaptchaFactory needs:

  • A word generator, to create the text to read.
  • A wordToImage, to generate the captcha from the text.
<bean id="CaptchaFactory" class="com.octo.captcha.image.gimpy.GimpyFactory" >
	<constructor-arg><ref bean="wordgen"/></constructor-arg>
	<constructor-arg><ref bean="wordtoimage"/></constructor-arg>
</bean>

A WordGenerator creates a text to be read, it can be random, be a common implementation take words from a list, and can make composition to create a text easier to read for a human being. In the example the WordGenerator needs a Dictionnary to get real words from.

<bean id="wordgen" class= "com.octo.captcha.component.word.wordgenerator.DictionaryWordGenerator" >
	<constructor-arg><ref bean="filedict"/></constructor-arg>
</bean>

A Dictionary provides words, this one reads words from the one provided by default, with almost 6000 english words.

<bean id="filedict" class="com.octo.captcha.component.word.FileDictionary" >
	<constructor-arg index="0"><value>toddlist</value></constructor-arg>
</bean>

After to other important part to create a factory, is the WordToImage component, which is mainly created with three others components:

  • A font generator
  • A background generator
  • A Text paster

This example is a bit more complex one; it takes the usual main three components, but also three deformations, to increase the power of captchas. All three are set to none, a component which creates no deformation, see below, and Examples to have more examples of deformations.

<bean id="wordtoimage" class="com.octo.captcha.component.image.wordtoimage.ComposedWordToImage" >
	<constructor-arg index="0"><ref bean="fontGenRandom"/></constructor-arg>
	<constructor-arg index="1"><ref bean="backGenUni"/></constructor-arg>
	<constructor-arg index="2"><ref bean="simpleWhitePaster"/></constructor-arg>
</bean>

A FontGenerator provide Fonts to a WordToImage, differents fonts increase the difficulties for cracking software using a learning process. This one generates random fonts from a list, and the first two arguments are the minimum size and the maximum size of the font.

<bean id="fontGenRandom" class="com.octo.captcha.component.image.fontgenerator.RandomFontGenerator" >
	<constructor-arg index="0"><value>40</value></constructor-arg>
	<constructor-arg index="1"><value>50</value></constructor-arg>
	<constructor-arg index="2">
		<list>
			<ref bean="fontArial"/>
		</list>
	</constructor-arg>
</bean>

A font is declared like this :

<bean id="fontArial" class="java.awt.Font" >
	<constructor-arg index="0"><value>Arial</value></constructor-arg>
	<constructor-arg index="1"><value>0</value></constructor-arg>
	<constructor-arg index="2"><value>10</value></constructor-arg>
</bean>

The BackgrountGenerator component can be very simple like in the example, single color, or more complex with real picture, or fancy computed shapes. The first two arguments are always, the size (length and height) of the resulting image.

<bean id="backGenUni" class="com.octo.captcha.component.image.backgroundgenerator.UniColorBackgroundGenerator" >
	<constructor-arg index="0"><value>300</value></constructor-arg>
	<constructor-arg index="1"><value>100</value></constructor-arg>
</bean>

The TextPaster, according to his name, pastes the text on the background. This can be done in a simple way, (see example below), or another implementation can paste each character randomly (but still readably), or can double the text to make computers more confused. TextPaster can be even decorated to put perturbations around the text, a component, TextDecorator, is designed for this purpose, see Annexes for some examples. Commons arguments for TextPaster are:
1. Minimal length of the text
2. Maximal length of the text
3. A color generator component to create the text color, see Annexes.

<bean id="simpleWhitePaster" class="com.octo.captcha.component.image.textpaster.SimpleTextPaster" >
	<constructor-arg type="java.lang.Integer" index="0">
		<value>3</value>
	</constructor-arg>
	<constructor-arg type="java.lang.Integer" index="1">
		<value>5</value>
	</constructor-arg>
	<constructor-arg type="java.awt.Color" index="2">
		<ref bean="colorGreen"/>
	</constructor-arg>
</bean>

And a color definition:

<bean id="colorGreen" class="java.awt.Color" >
	<constructor-arg index="0"><value>0</value></constructor-arg>
	<constructor-arg index="1"><value>255</value></constructor-arg>
	<constructor-arg index="2"><value>0</value></constructor-arg>
</bean>

Now we are ready to setup the CaptchaService singleton...

Setting the CaptchaService in Spring

This component has a special section because it is the top level component, which is actually manipulated within your application. As for the engine, there is many already configured CaptchaService, but the one we need is a custom one, which takes an CaptchaEngine, as you can see, the BufferedEngineContairner, is in fact CaptchaEngine.

The arg index1 is the captcha session expering time, in seconds. Next arg especifies the maximun storage size.

<bean id="captchaService" class="com.octo.captcha.service.multitype.GenericManageableCaptchaService">
	<constructor-arg index="0"><ref bean="imageEngine"/></constructor-arg>
	<constructor-arg index="1"><value>180</value></constructor-arg>
	<constructor-arg index="2"><value>180000</value></constructor-arg>
</bean>

Other links, resources:

Please refers to External resources

  • No labels

7 Comments

  1. Can any one help me in integrating Jcaptcha with Spring and Acegi as I am new to these concepts. I have to implement security in my new project which uses Spring frame work.

  2. Well,

    more details regarding the 5-sec and 10-sec tutorials would be helpful. Could anyone explain to me why this doesn't work? I receive an empty captcha image.

     web.xml

     <?xml version="1.0" encoding="UTF-8"?>
    <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
                                        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                                        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/applicationContext.xml</param-value>
        </context-param>
        <context-param>
            <param-name>webAppRootKey</param-name>
            <param-value>captcha.root</param-value>
        </context-param>
    
        <listener>
            <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
        </listener>
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
    
        <servlet>
            <servlet-name>dispatcher</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <load-on-startup>2</load-on-startup>
        </servlet>
    
        <servlet-mapping>
            <servlet-name>dispatcher</servlet-name>
            <url-pattern>*.htm</url-pattern>
        </servlet-mapping>
    
        <session-config>
            <session-timeout>30</session-timeout>
        </session-config>
    
        <welcome-file-list>
            <welcome-file>redirect.jsp</welcome-file>
        </welcome-file-list>
    
        <error-page>
            <exception-type>java.lang.Throwable</exception-type>
               <location>/error.html</location>
        </error-page>
    </web-app>
    

    dispatcher-servlet.xml

     <?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:p="http://www.springframework.org/schema/p"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
                               http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
                               http://www.springframework.org/schema/aop
                               http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
                               http://www.springframework.org/schema/tx
                               http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
    
        <bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/>
    <!--
    	<bean id="captchaEngine" class="com.octo.captcha.engine.image.gimpy.SimpleListImageCaptchaEngine"/>
    
    	<bean id="captchaService" name="/captcha.htm"
    		class="com.octo.captcha.service.image.DefaultManageableImageCaptchaService">
    		<property name="captchaEngine" ref="captchaEngine"/>
    	</bean>
    -->
        <bean name="/captcha.htm" class="com.octo.captcha.service.image.DefaultManageableImageCaptchaService" id="imageCaptchaService"/>
    
        <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
            <property name="mappings">
                <props>
                    <prop key="index.htm">indexController</prop>
                </props>
            </property>
        </bean>
    
        <bean id="viewResolver"
              class="org.springframework.web.servlet.view.InternalResourceViewResolver"
              p:prefix="/WEB-INF/jsp/"
              p:suffix=".jsp" />
    
        <bean name="indexController"
              class="org.springframework.web.servlet.mvc.ParameterizableViewController"
              p:viewName="index" />
    
    </beans>
    

     index.jsp

    <%@ page session="false" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
    <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <%@ taglib prefix="f" uri="http://java.sun.com/jsp/jstl/fmt" %>
    <html>
        <head>
            <title>Captcha</title>
        </head>
        <body>
            <form method="post">
                <img src="captcha.htm"/><input type="text" name="j_captcha_response" /><input type="submit" value="Submit" name="_finish"/>
            </form>
        </body>
    </html>
    


  3. Two major parts for generating a captcha image are missing from the tutorial:

    Controller class responsible for image generation

    import java.awt.image.BufferedImage;
    import java.io.ByteArrayOutputStream;
    
    import javax.imageio.ImageIO;
    import javax.servlet.ServletOutputStream;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.i18n.LocaleContextHolder;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import com.octo.captcha.service.CaptchaServiceException;
    import com.octo.captcha.service.multitype.MultiTypeCaptchaService;
    
    /**
     * Generates captcha image to tell whether its user is a human or a computer.
     * See http://forge.octo.com/jcaptcha/confluence/display/general/Home
     */
    @Controller
    public class CaptchaController {
     public static final String CAPTCHA_IMAGE_FORMAT = "jpeg";
    
     @Autowired
     private MultiTypeCaptchaService captchaService;
    
     @RequestMapping("/captcha.html")
     public void showForm(HttpServletRequest request, HttpServletResponse response) throws Exception {
      byte[] captchaChallengeAsJpeg = null;
      // the output stream to render the captcha image as jpeg into
      ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();  
      try {
       // get the session id that will identify the generated captcha.
       // the same id must be used to validate the response, the session id is a good candidate!
    
       String captchaId = request.getSession().getId();
       BufferedImage challenge = captchaService.getImageChallengeForID(captchaId, request.getLocale());  
    
       ImageIO.write(challenge, CAPTCHA_IMAGE_FORMAT, jpegOutputStream);
      } catch (IllegalArgumentException e) {
       response.sendError(HttpServletResponse.SC_NOT_FOUND);  
       return;
      } catch (CaptchaServiceException e) {
       response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);  
       return;
      }
    
      captchaChallengeAsJpeg = jpegOutputStream.toByteArray();  
    
      // flush it in the response  
      response.setHeader("Cache-Control", "no-store");  
      response.setHeader("Pragma", "no-cache");  
      response.setDateHeader("Expires", 0);  
      response.setContentType("image/"+CAPTCHA_IMAGE_FORMAT);  
    
      ServletOutputStream responseOutputStream = response.getOutputStream();  
      responseOutputStream.write(captchaChallengeAsJpeg);  
      responseOutputStream.flush();  
      responseOutputStream.close();
     }
    }
    

    Verifying the captcha input code

    import java.util.Date;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.i18n.LocaleContextHolder;
    import org.springframework.security.providers.encoding.PasswordEncoder;
    import org.springframework.stereotype.Controller;
    import org.springframework.validation.BindingResult;
    import org.springframework.validation.Validator;
    import org.springframework.web.bind.WebDataBinder;
    import org.springframework.web.bind.annotation.InitBinder;
    import org.springframework.web.bind.annotation.ModelAttribute;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.SessionAttributes;
    import org.springframework.web.bind.support.SessionStatus;
    
    @Controller
    @RequestMapping("/registration.html")
    @SessionAttributes("registrationForm")
    public class RegistrationController {
     private static Logger logger = LoggerFactory.getLogger(RegistrationController.class);
    
     public static final String DEFAULT_ERROR_VIEW = "registration";
     public static final String FORM_NAME = "registrationForm";
    
     @Autowired
     private Validator validator;
    
     @Autowired
     private MultiTypeCaptchaService captchaService;
    
     @RequestMapping(method = RequestMethod.GET)
     public @ModelAttribute(FORM_NAME) RegistrationForm showForm(HttpServletRequest request) {
      RegistrationForm form = (RegistrationForm)request.getSession().getAttribute(FORM_NAME);
      if (null == form) form = new RegistrationForm();
      return form;
     }
    
     @RequestMapping(method = RequestMethod.POST)
     public String processForm(
       @ModelAttribute(FORM_NAME) RegistrationForm form,
       BindingResult result, SessionStatus status, HttpServletRequest request) {
      validator.validate(form, result);
      validateCaptcha(form, result, request.getSession().getId(), "registration.captcha");
      if (result.hasErrors()) {
       return DEFAULT_ERROR_VIEW;
      }
      ....
     }
     ....
    
     protected void validateCaptcha(RegistrationForm registrationForm, BindingResult result, String sessionId, String errorCode) {
      // If the captcha field is already rejected
      if (null != result.getFieldError("captcha")) return;
      boolean validCaptcha = false;
      try {
       validCaptcha = captchaService.validateResponseForID(sessionId, registrationForm.getCaptcha());
      }
      catch (CaptchaServiceException e) {
       //should not happen, may be thrown if the id is not valid
       logger.warn("validateCaptcha()", e);
      }
      if (!validCaptcha) {
       result.rejectValue("captcha", errorCode);
      }
     }
    }
    

    There are two bugs in the tutorial:

    java.awt.Color in JDK 1.6 : The type has to be set.

    <bean id="backgroundColor" class="java.awt.Color" >
     <constructor-arg type="int" index="0" value="230"/> <\!-\- red -->
     <constructor-arg type="int" index="1" value="230"/> <\!-\- green -->
     <constructor-arg type="int" index="2" value="230"/> <\!-\- blue -->
    </bean>
    

    GenericManageableCaptchaService : it has 4 arguements in the constructor.

    <bean id="captchaService" class="com.octo.captcha.service.multitype.GenericManageableCaptchaService">
     <constructor-arg index="0" ref="imageEngine"/>
     <constructor-arg type="int" index="1" value="180"/> <!-- minGuarantedStorageDelayInSeconds -->
     <constructor-arg type="int" index="2" value="180000"/> <!-- maxCaptchaStoreSize -->
     <constructor-arg type="int" index="3" value="75000"/> <!-- captchaStoreLoadBeforeGarbageCollection -->
    </bean>
    

    Afterwards the captcha image can be inserted in the JSP file:

     <s:message var="captchaTitle" code="captcha.title"/>
     <img src="captcha.html" title="${captchaTitle}" alt="${captchaTitle}"/>
    
  4. Naz

    Hello,

    I am trying to integrate JCaptcha step by step in my application which uses Spring. I have added this line to my url-mapping.xml.

    <bean class="com.octo.captcha.service.image.DefaultManageableImageCaptchaService" id="imageCaptchaService"/>

    but when starting the app I get following error:

    org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'imageCaptchaService' defined in ServletContext resource /WEB-INF/conf/spring/web/url-mapping.xml: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Could not instantiate bean class com.octo.captcha.service.image.DefaultManageableImageCaptchaService: Constructor threw exception; nested exception is java.lang.NoSuchMethodError: com.jhlabs.image.WaterFilter.setAmplitude(D)V

    I am using jcaptch1.0.

    Any help is appreciated.

    Thanks,
    N

    1. Naz

      basically in details it says:

      Caused by: java.lang.NoSuchMethodError: com.jhlabs.image.WaterFilter.setAmplitude(D)V

      1. Naz

        Using JCaptcha2 it says

        org.springframework.beans.factory.CannotLoadBeanClassException: Error loading class com.octo.captcha.engine.image.gimpy.SimpleListImageCaptchaEngine for bean with name 'captchaEngine' defined in ServletContext resource /WEB-INF/conf/spring/web/url-mapping.xml: problem with class file or dependent class; nested exception is java.lang.UnsupportedClassVersionError: Bad version number in .class file (unable to load class com.octo.captcha.engine.image.gimpy.SimpleListImageCaptchaEngine)

        My JDK is 1.5!!

        It is driving me crazy (smile)

      2. My guess is that you don't have the imaging-01012005.jar, which is required for catcha.jar.

        <dependency>
         <groupId>com.jhlabs</groupId>
         <artifactId>imaging</artifactId>
         <version>01012005</version>
        </dependency>
        

        It can be taken from http://maven.jahia.org/maven2/com/jhlabs/imaging/01012005/