Rodrigo Guedes de Souza

Desenvolvedor e apaixonado por tecnologia.

Utilizando Captcha Com Servlet

Um assunto muito interessante que eu trato neste post, é como utilizar captcha para manter seguro seu site contra robôs da web e ataques.

O que é um Robô da Web

ROBOT (ou robô) é um programa de computador que percorre automaticamente as páginas da Internet em busca de documentos, a fim de indexá-los, validá-los ou monitorar alterações de conteúdo. Wikipedia

O que é DDos-Spam

Um ataque de negação de serviço (também conhecido como DoS, um acrônimo em inglês para Denial of Service), é uma tentativa em tornar os recursos de um sistema indisponíveis para seus utilizadores. Alvos típicos são servidores web, e o ataque tenta tornar as páginas hospedadas indisponíveis na WWW. Não se trata de uma invasão do sistema, mas sim da sua invalidação por sobrecarga. Wikipedia

O que é Captcha

CAPTCHA é um acrônimo para “Completely Automated Public Turing test to tell Computers and Humans Apart” (teste de Turing público completamente automatizado para diferenciar entre computadores e humanos) desenvolvido pela universidade do Carnegie-Mellon.

CAPTCHAs são utilizados para impedir que softwares automatizados execute ações que degradam a qualidade do serviço de um sistema dado, devido à despesa do abuso ou do recurso. Embora CAPTCHAs sejam utilizados mais frequentemente como uma resposta a proteção de interesses comerciais, a noção que existem para parar somente spammers é um erro, ou uma simples redução. Wikipedia

API Disponíveis Atualmente as apis mais utilizadas, freqüentemente, em sistemas em java são:

Simple Captcha http://simplecaptcha.sourceforge.net Última Atualização 25/09/2005

JCaptcha http://jcaptcha.sourceforge.net Última Atualização 03/05/2007

SkewPassImage http://skewpassim.sourceforge.net Última Atualização 27/03/2006

Recaptcha http://www.recaptcha.net

Implementação Uma situação real em que pode ser utilizado a captcha, é em um cadastro de usuário em um fórum, por exemplo, imagine se não existir um método que empeça de um cracker ou um aplicativo robô, derrubar seu site de tanto emitir submissões de cadastros de usuário “Ataque por DDos-spam”. Isso poderia gerar um excessivo uso de recursos desnecessário do servidor. Para nossa implementação eu escolho o Jcaptcha, pois na minha opinião é a API mais completa para se trabalhar com Java.

CaptchaServiceSingleton.java
1
2
3
4
5
6
7
8
9
10
11
12
13
package br.com.guedesdesouza.captcha;

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

public class CaptchaServiceSingleton {

    private static ImageCaptchaService instance = new DefaultManageableImageCaptchaService();

    public static ImageCaptchaService getInstance(){
        return instance;
    }
}
GeraCaptchaServlet.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package br.com.guedesdesouza.captcha;

import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.octo.captcha.service.CaptchaServiceException;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;

public class GeraCaptchaServlet extends HttpServlet {

    @Override
    public void init(ServletConfig config) throws ServletException {
      super.init(config);
  }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
      byte[] captchaChallengeAsJpeg = null;
      // Objeto que receber a imagem jpeg gerada pelo JCaptcha
      // retorna para solicitação do cliente
      ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
      try {
          // Pega o id da sessao para utiliza como identificador
          // para geração do captcha
          String captchaId = req.getSession().getId();
          // chama o método de geração de captcha
          BufferedImage challenge = CaptchaServiceSingleton.getInstance().getImageChallengeForID(captchaId,req.getLocale());

          // codifica a imagem para tipo jpeg
          JPEGImageEncoder jpegEncoder = JPEGCodec.createJPEGEncoder(jpegOutputStream);
          jpegEncoder.encode(challenge);
      } catch (IllegalArgumentException e) {
          resp.sendError(HttpServletResponse.SC_NOT_FOUND);
          return;
      } catch (CaptchaServiceException e) {
          resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
          return;
      }

      captchaChallengeAsJpeg = jpegOutputStream.toByteArray();

      // enviar a resposta com a imagem
      resp.setHeader("Cache-Control", "no-store");
      resp.setHeader("Pragma", "no-cache");
      resp.setDateHeader("Expires", 0);
      resp.setContentType("image/jpeg");
      ServletOutputStream responseOutputStream = resp.getOutputStream();
      responseOutputStream.write(captchaChallengeAsJpeg);
      responseOutputStream.flush();
      responseOutputStream.close();
  }
}
ValidaCaptchaRegistroServlet.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package br.com.guedesdesouza.captcha;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ValidaCaptchaRegistroServlet extends HttpServlet{

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {
  
      String resposta = req.getParameter("captcha.id");
      String idCaptcha = req.getSession().getId();
      
      RequestDispatcher rd = null;
      
      if (isValido(resposta, idCaptcha)){//se verdadeiro conclui cadastro
          rd = getServletContext().getRequestDispatcher("/concluido.jsp");
          rd.forward(req, resp);
      }else{// caso seja inválido retorna a página de registro
          req.setAttribute("msg","Texto da imagem digitado errado");
          rd = getServletContext().getRequestDispatcher("/registrar.jsp");
          rd.forward(req, resp);
      }
  }
      
  private boolean isValido(String resposta, String idCaptcha){
      boolean resultado = false;
      try{
          resultado = CaptchaServiceSingleton.getInstance().validateResponseForID(idCaptcha,resposta);
      }catch (Exception e) {
          // TODO: handle exception
      }
      return resultado;
  }
}
registro.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<?xml version="1.0" encoding="ISO-8859-1" ?>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="f" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<style type="text/css">@import url( http://styles/style.css);</style>
<title>Registrar</title>
</head>
<body>

<form name="formregister" action="validacaptcha" method="post">
<table class="forumline" cellspacing="1" cellpadding="3" width="50%" border="0" align="center">
<tr>
<th class="thhead" valign="middle" colspan="2" height="25">Registrar Informação</th>
</tr>

<tr>
<td class="row2" colspan="2" align="center"><span class="gensmall"><font color="red">Você deve preencher os campos com "*"</font></span></td>

</tr>

<tr>
<td class="row1" width="38%" align="right">Usuário: *</td>
<td class="row2"><input class="post" type="text" style="WIDTH: 200px" maxlength="25" size="25" name="username" value=""/></td>
</tr>

<tr>
<td class="row1" align="right">Endereço de e-mail: *</td>
<td class="row2"><input class="post" type="text" style="WIDTH: 200px" maxlength="255" size="25" name="email" value=""/></td>

</tr>

<tr>
<td class="row1" align="right">Senha: *</td>
<td class="row2"><input name="password" type="password" class="post" id="password" style="WIDTH: 200px" size="25" maxlength="100" /> </td>
</tr>

<tr>
<td class="row1" align="right">Confirme a senha: *</td>

<td class="row2"><input class="post" style="WIDTH: 200px" type="password" maxlength="100" size="25" name="password_confirm" /> </td>
</tr>

<tr>
<td class="row1" align="right"></td>
<td class="row2"><img src="geracaptcha" /></td>
</tr>

<tr>
<td class="row1" align="right">Digite o texto da image: *</td>

<td class="row2"><input class="post" type="text" style="WIDTH: 200px" maxlength="7" size="25" name="captcha.id" value=""/></td>
</tr>

<tr>
<td class="row2" colspan="2" align="center"><font color="#ff0000"><b>
<c:out value="${msg}"></c:out>
</b></font></td>
</tr>

<tr align="center">
<td class="catbottom" colspan="2" height="28">
<input class="mainoption" type="submit" value="Enviar" name="submit" />
<input class="liteoption" type="reset" value="Limpar" name="reset" />
</td>
</tr>
</table>
</form>
</body>
</html>
concluido.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="ISO-8859-1" ?>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<title>Registro Concluído</title>
</head>
<body>
Registro Concluído...
</body>
</html>
web.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>captcha</display-name>

<servlet>
<servlet-name>geracaptchaServlet</servlet-name>
<servlet-class>br.com.guedesdesouza.captcha.GeraCaptchaServlet</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>

<servlet>
<servlet-name>validacaptchaServlet</servlet-name>
<servlet-class>br.com.guedesdesouza.captcha.ValidaCaptchaRegistroServlet</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>geracaptchaServlet</servlet-name>
<url-pattern>/geracaptcha</url-pattern>
</servlet-mapping>

<servlet-mapping>
<servlet-name>validacaptchaServlet</servlet-name>
<url-pattern>/validacaptcha</url-pattern>
</servlet-mapping>

</web-app>

E o resultado é esse:

Apesar de um captcha ser útil para minimizar os problemas de ataque ou vasculhamento de informações por robôs dos grandes motores de buscas da internet e garantir que somente seres humanos possa responder as peguntas geradas, ele também tem uma problema: Como o captcha se baseia na geração de imagens para definição da pergunta, isso se torna um empecilho para usuário com deficiências visuais, pois, para que possam navegar na internet, utilizam-se de software especializados em ler o conteúdo do html e como determinado software não consegue ler a imagem gerada, o deficiente acaba não tendo acesso sobre o conteúdo.Num próximo post irei um pouco mais a fundo do Jcaptcha para podermos utilizar alguns recursos personalizados. Código Fonte em: https://github.com/gulira/captcha