Wednesday, June 2, 2010

Death by Captcha?

Captcha is a type of challenge-response test used in computing to ensure that the response is not generated by a computer. The process usually involves one computer (a server) asking a user to complete a simple test which the computer is able to generate and grade. Because other computers are unable to solve the CAPTCHA, any user entering a correct solution is presumed to be human. Captcha has a lot of uses for web applications.

In this tutorial, we are going I am going to show you one way of doing it. I'll be showing you how to integrate JCaptcha into our Tapestry5 webapp as a web service. Mr. Ship, the creator of Tapestry also a tutorial for this, its up in his blog. He is using Kaptcha.

Now to begin with this tutorial we need to add JCaptcha as a dependency to our project. So, open up pom.xml in project files then add these lines:

 <dependencies>  
 ....  
 ....  
 <dependency>  
    <groupId>com.octo.captcha</groupId>  
    <artifactId>jcaptcha</artifactId>  
    <version>1.0</version>  
 </dependency>  
 </dependencies>  

Don't forget to save it.

The whole magic of our JCaptcha service is the interface which has two methods. In you edu.addressbook.services package create a new interface and name it JCaptchaService. The contents of which would look:

 package edu.addressbook.services;  
 import org.apache.tapestry5.StreamResponse;  
 public interface JCaptchaService {  
   public boolean isValidUserResponse(String verificationCode, String captchaId);  
   public StreamResponse generateImageChallenge(String captchaId);  
 }  

You will notice that our interface has a isValidUserResponse() method - this is where our challenge is invoked. If it returns false then apparently the user on the other side might not human else if its true then we assume that the user is human.

Following the pattern, you'll have to create a edu.addressbook.services.impl package which contains a single class: JCaptchaServiceImpl. You need to understand that we still have to do this since JCaptcha generates a StreamResponse object (via the ImageCaptchaService object) which we can redirect into Tapestry.

 public class JCaptchaServiceImpl implements JCaptchaService {  
   private final ImageCaptchaService serviceInstance;  
   public JCaptchaServiceImpl(ImageCaptchaService serviceInstance) {  
     this.serviceInstance = serviceInstance;  
   }  
   public StreamResponse generateImageChallenge(String captchaId) {  
     BufferedImage imageChallenge = serviceInstance.getImageChallengeForID(captchaId, Locale.US);  
     ByteArrayOutputStream os = new ByteArrayOutputStream();  
     try {  
       ImageIO.write(imageChallenge, "PNG", os);  
       final InputStream is = new ByteArrayInputStream(os.toByteArray());  
       return new StreamResponse() {  
         public String getContentType() {  
           return "image/png";  
         }  
         public InputStream getStream() throws IOException {  
           return is;  
         }  
         public void prepareResponse(Response response) {  
           response.setHeader("Pragma", "no-cache");  
           response.setHeader("Cache-Control", "no-cache");  
           response.setDateHeader("Expires", 0);  
         }  
       };  
     } catch (IOException e) {  
       throw new RuntimeException(e);  
     }  
   }  
   public boolean isValidUserResponse(String verificationCode, String captchaId) {  
     try {  
       return serviceInstance.validateResponseForID(captchaId, verificationCode);  
     } catch (CaptchaServiceException e) {  
       throw new RuntimeException(e);  
     }  
   }  
   public class ImageCaptchaImpl extends ImageCaptcha {  
     public static final long serialVersionUID = 1;  
     public ImageCaptchaImpl(String question, BufferedImage challenge) {  
       super(question, challenge);  
     }  
     public Boolean validateResponse(Object obj) {  
       return Boolean.FALSE;  
     }  
   }  
 }  

And for the final part is positioning it in the front-end. I put this in the index.tml (and its partner index.java). The whole thing is "injected" into the page like this:

   @Inject  
   private JCaptchaService captchaService;  
   @Inject  
   private RequestGlobals requestGlobals;  

and its the public methods are:

   public Link getImageURL() {  
     return resources.createActionLink("image", false);  
   }  
   //executes when image is rendered  
   public Object onImage() {  
     return captchaService.generateImageChallenge(requestGlobals.getHTTPServletRequest().getSession().getId());  
   }  

This way we just make a call to the getImageURL() method in the webpage via an expansion and voila! we have a captcha image to filter which users are human or not. Here is the final line of code to be placed in the index.tml file:

  <p>Captcha Image</p>  
  <t:img src="${imageURL}" align="absmiddle" alt="Dynamic Verification Code" />  

That it.

Now remember that you can get the code at kenai using the SVN: https://svn.kenai.com/svn/addressbook-tap5~svn

The End.