Implementando Captcha com Servlets

 

Captcha (Completely Automated Public Turing test to tell Computers and Humans Apart) é uma técnica utilizada para verificar se quem está requisitando um determinado serviço do software é um usuário, e não uma aplicação robô. A idéia basicamente é apresentar caracteres de validação para que sejam digitados pelo usuário para serem validados pela aplicação, esses caracteres devem ser legíveis a humanos, mas não para aplicações. Geralmente isso é feito através de imagens com caracteres distorcidos.

 

A técnica é muito utilizada para evitar spam em listas de discussão, chats, e telas de cadastro que não requerem login.

 

Existem disponíveis algumas API’s para implementação de captcha em Java, uma delas é a JCaptcha, que usaremos nesse artigo.

 

Como exemplo, tomaremos uma rela de comentário de um blog, colocaremos captcha para evitar que sejam postadas mensagens por rotinas automatizadas, gerando spam na lista de comentários.

 

Estrutura

Para iniciar a aplicação criaremos a estrutura básica de uma aplicação web, crie na pasta webapps do Tomcat de acordo com a estrutura mostrada na Figura 01.

 

jelton1.JPG

Figura 01. Estrutura de diretórios.

 

Faça o download da API JCaptcha no seguinte endereço: http://prdownloads.sourceforge.net/jcaptcha/jcaptcha-bin-1.0-RC3.zip?download

 

Descompacte o arquivo .zip, pegue o arquivo jcaptcha-all-1.0-RC3.jar e coloque na pasta lib da aplicação.

 

POJO

Criaremos também uma classe Comentario que será o POJO do nosso cadastro. Como não é o objetivo desse artigo, não iremos persistir esses objetos, mas logicamente em uma aplicação real você teria que fazer isso.


                package br.com.javamagazine.jairelton2;

 

public class Comentario {

      private int id;

      private String nome;

      private String email;

      private String texto;

 

      public Comentario(){

            nome = "";

            email = "";

            texto = "";      

      }

 

      public String getEmail() {

            return email;

      }

      public void setEmail(String email) {

            this.email = email;

      }

      public int getId() {

            return id;

      }

      public void setId(int id) {

            this.id = id;

      }

      public String getNome() {

            return nome;

      }

      public void setNome(String nome) {

            this.nome = nome;

      }

      public String getTexto() {

            return texto;

      }

      public void setTexto(String texto) {

            this.texto = texto;

      }

}
            

Classe de criação e validação dos caracteres

Precisaremos também de uma classe para gerenciar a criação das imagens e a validação dos caracteres, chamaremos a classe de Captcha:


                package br.com.javamagazine.jairelton2;

 

import java.awt.Color;

 

import com.octo.captcha.component.image.backgroundgenerator.BackgroundGenerator;

import com.octo.captcha.component.image.backgroundgenerator.

                                        FunkyBackgroundGenerator;

import com.octo.captcha.component.image.fontgenerator.FontGenerator;

import com.octo.captcha.component.image.fontgenerator.

                                        TwistedAndShearedRandomFontGenerator;

import com.octo.captcha.component.image.textpaster.RandomTextPaster;

import com.octo.captcha.component.image.textpaster.TextPaster;

import com.octo.captcha.component.image.wordtoimage.ComposedWordToImage;

import com.octo.captcha.component.image.wordtoimage.WordToImage;

import com.octo.captcha.component.word.wordgenerator.RandomWordGenerator;

import com.octo.captcha.engine.image.ListImageCaptchaEngine;

import com.octo.captcha.image.gimpy.GimpyFactory;

import com.octo.captcha.service.image.DefaultManageableImageCaptchaService;

import com.octo.captcha.service.image.ImageCaptchaService;

 

public class Captcha {

      private ImageCaptchaService service;

 

      private static Captcha instance = new Captcha();

     

/*A classe deve ser um Singleton*/

      private Captcha(){

            DefaultManageableImageCaptchaService serv =

                                  new DefaultManageableImageCaptchaService();

            serv.setCaptchaEngine(new EngineNumeros());

           

            service = serv;

      }

 

      public static Captcha getInstance(){

            return instance;

      }

     

      public ImageCaptchaService getService(){

            return service;

      }

 

      /*Especializando um Engine para gerar apenas números*/

      class EngineNumeros extends ListImageCaptchaEngine {

            protected void buildInitialFactories() {

                     /*Cria um um TextPaster, o tamanho mínimo é de 5 caracteres

                      * Maximo de 8, e a cor do texto será branca

                      */

                     TextPaster textPaster =

                                new RandomTextPaster(5, 8, Color.white);

                    /*Um gerador de background, a imagem terá 100 x 50 pixels

                     */

                     BackgroundGenerator backgroundGenerator =

                                         new FunkyBackgroundGenerator(100, 50);

                     /* Um gerador de fonte, é responsável por distorcer o

                      * texto, o tamanho mínimo da fonte é 25 e o Maximo 30

      */

                     FontGenerator fontGenerator =

                              new TwistedAndShearedRandomFontGenerator(25, 30);

                     /* O objeto responsável por juntar o background, a fonte e

                      * o texto para gerar a imagem

                      */

                     WordToImage wordToImage =

                                     new ComposedWordToImage(fontGenerator,

                                     backgroundGenerator, textPaster);

                     /* Adiciona o Factory RandomWordGenerator recebe os

                      * caracteres válidos, no caso queremos apenas números

          */

                     this.addFactory(new GimpyFactory(new RandomWordGenerator

                                                ("0123456789"), wordToImage));

            }

       }   

}
            

Servlet de geração da imagem

Temos que criar também um Servlet para a geração da imagem, veja o código abaixo:

Servlet de cadastro

Criaremos também um Servlet para onde serão enviados os dados do formulário de comentários, esse Servlet validará os caracteres digitados pelo usuário, permitindo ou não o cadastro do comentário de acordo com o resultado da validação:


                package br.com.javamagazine.jairelton2;

 

import java.awt.image.BufferedImage;

import java.io.ByteArrayOutputStream;

import java.io.IOException;

 

import javax.servlet.ServletException;

import javax.servlet.ServletOutputStream;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

 

import com.sun.image.codec.jpeg.JPEGCodec;

import com.sun.image.codec.jpeg.JPEGImageEncoder;

 

 public class CaptchaServlet extends javax.servlet.http.HttpServlet{

      protected void doGet(HttpServletRequest request, HttpServletResponse

                           response) throws ServletException, IOException {

            ServletOutputStream out = response.getOutputStream();

            ByteArrayOutputStream jpegStream = new ByteArrayOutputStream();

            try {

                  /*A imagem será gerada com base no ID da sessão*/

                  String jsid = request.getSession().getId();

                  /*Gera a imagem*/

                  BufferedImage challenge = Captcha.getInstance().

                                    getService().getImageChallengeForID(jsid);

                  /*Codifica a imagem no formato JPEG*/

                  JPEGImageEncoder jpegEncoder = JPEGCodec.

                                                createJPEGEncoder(jpegStream);

                  jpegEncoder.encode(challenge);

            } catch (Exception e) {

               response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);

               return;

            }

            /*Transforma a imagem em um array de bytes*/

            byte[] jpegBytes = jpegStream.toByteArray();

            /*Modifica os cabeçalhos http

* para que a imagem não seja armazenada em cache

*/

            response.setHeader("Cache-Control", "no-store");

            response.setHeader("Pragma", "no-cache");

            response.setDateHeader("Expires", 0);

            response.setContentType("image/jpeg");

            /*Envia a imagem para o cliente*/

                  out.write(jpegBytes);

                  out.flush();

                  out.close();

      }    

     

      protected void doPost(HttpServletRequest request, HttpServletResponse

                            response) throws ServletException, IOException {

            doGet(request, response);

      }               

}

 
            

Configuração

Vejamos agora a configuração do arquivo web.xml, esse arquivo deve estar dentro da pasta WEB-INF da aplicação:

 

cod1art2jelton.JPG

 

Veja que o servlet CaptchaServlet é mapeado para a url /captcha.jpg, isso é interessante para mascarar a implementação do Captcha, parecendo ser apenas uma imagem simples.

 

Formulário

Como o Servlet acima vai receber os dados do formulário, logicamente precisamos criar esse formulário, faremos com JSP, crie a página formulario.jsp na pasta da aplicação:

 

cod21art2jelton.JPG

cod22art2jelton.JPG
cod23art2jelton.JPG

 

Note que no trecho <img src="captcha.jpg" /> é inserida uma imagem na página apontando para captcha.jpg, isso não é uma imagem comum e sim o servlet CaptchaServlet que devolverá uma imagem normalmente, mascarando a implementação como dito anteriormente.

 

Página de confirmação

Por fim, criaremos uma segunda página JSP para exibir os dados cadastrados em caso de sucesso, em uma aplicação real, essa seria a página de confirmação do comentário. A página deve ser criada na pasta da aplicação com o nome de ok.jsp:


cod3art2jelton.JPG

 

Teste

Pronto, nossa implementação de captcha já está feita, inicie o servidor Tomcat e acesse o endereço http://localhost:8080/JavaMagazine2/formulario.jsp , será exibida a página apresentada na Figura 02.

 

jelton2.JPG

Figura 02. Página da aplicação

 

Digitando os caracteres corretamente você será redirecionado para a página mostrando os dados do seu comentário, caso contrário será mostrada uma mensagem de erro.

 

Essa é uma forma simples de evitar transtornos com spam e acessos indevidos a páginas, no exemplo de comentários, mas há muitas outras aplicações. A API JCaptcha permite varias costumizações inclusive para implementação por som, para mais detalhes consulte a documentação no site http://www.jcaptcha.org.