Captchas gráficos são um requisito comum em aplicações web para segurança. Existem diversas abordagens para gerá-los, e este artigo detalha um método rápido utilizando o componente Kaptcha do Google.
O Kaptcha é uma bilbioteca de código aberto do Google, conhecida por sua simplicidade e facilidade de integração. Sua filosofia "convenção sobre configuração" permite uma rápida adaptação a projetos existentes.
Vamos ao passo a passo de como utilizá-lo:
1. Adicionando a Dependência do Kaptcha
Primeiro, inclua a seguinte dependência Maven em seu projeto Spring Boot:
<dependency>
<groupId>com.google.code.kaptcha</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3</version>
</dependency>
Caso encontre dificuldades para baixar a versão acima, uma alternativa é a seguinte configuração:
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
2. Configurando o Kaptcha no Spring Boot
Crie uma classe de configuração para registrar o bean necessário para o Kaptcha. Esta etapa pode ser integrada com um servidor de configuração centralizado.
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
@Configuration
public class KaptchaConfig {
@Bean
public DefaultKaptcha defaultKaptcha() {
DefaultKaptcha kaptcha = new DefaultKaptcha();
Properties properties = new Properties();
properties.put("kaptcha.border", "no");
properties.put("kaptcha.textproducer.font.color", "blue");
properties.put("kaptcha.image.width", "200");
properties.put("kaptcha.image.height", "60");
properties.put("kaptcha.textproducer.font.size", "35");
properties.put("kaptcha.session.key", "verifyCodeSession");
properties.put("kaptcha.textproducer.char.space", "5");
properties.put("kaptcha.obscurificator.impl", "com.google.code.kaptcha.impl.WaterRipple");
properties.put("kaptcha.background.clear.from", "lightblue");
properties.put("kaptcha.background.clear.to", "white");
Config config = new Config(properties);
kaptcha.setConfig(config);
return kaptcha;
}
}
Opções de Configuração do Kaptcha
As propriedades configuráveis e suas funções são:
| Propriedade | Descrição | Valor Padrão |
|---|---|---|
kaptcha.border |
Define se a imagem terá borda (yes ou no). |
yes |
kaptcha.border.color |
Cor da borda (r,g,b ou nomes como white, black). |
black |
kaptcha.image.width |
Largura da imagem do captcha. | 200 |
kaptcha.image.height |
Altura da imagem do captcha. | 50 |
kaptcha.producer.impl |
Classe de implementação para a produção da imagem. | com.google.code.kaptcha.impl.DefaultKaptcha |
kaptcha.textproducer.impl |
Classe de implementação para a produção do texto. | com.google.code.kaptcha.text.impl.DefaultTextCreator |
kaptcha.textproducer.char.string |
Conjunto de caracteres a serem usados no captcha. | abcde2345678gfynmnpwx |
kaptcha.textproducer.char.length |
Número de caracteres no captcha. | 5 |
kaptcha.textproducer.font.names |
Nomes das fontes a serem utilizadas. | Arial, Courier |
kaptcha.textproducer.font.size |
Tamanho da fonte. | 40px |
kaptcha.textproducer.font.color |
Cor da fonte (r,g,b ou nomes como white, black). |
black |
kaptcha.textproducer.char.space |
Espaçamento entre os caracteres. | 2 |
kaptcha.noise.impl |
Classe de impelmentação para o ruído/interferência na imagem. | com.google.code.kaptcha.impl.DefaultNoise |
kaptcha.noise.color |
Cor do ruído (r,g,b ou nomes como white, black). |
black |
kaptcha.obscurificator.impl |
Estilo de obscuridade da imagem (WaterRipple, FishEyeGimpy, ShadowGimpy). |
com.google.code.kaptcha.impl.WaterRipple |
kaptcha.background.impl |
Classe de implementação para o fundo da imagem. | com.google.code.kaptcha.impl.DefaultBackground |
kaptcha.background.clear.from |
Cor inicial para o gradiente de fundo. | light grey |
kaptcha.background.clear.to |
Cor final para o gradiente de fundo. | white |
kaptcha.word.impl |
Renderizador de texto. | com.google.code.kaptcha.text.impl.DefaultWordRenderer |
kaptcha.session.key |
Chave usada para armazenar o captcha na sessão. | KAPTCHA_SESSION_KEY |
kaptcha.session.date |
Chave usada para armazenar a data de expiração na sessão. | KAPTCHA_SESSION_DATE |
3. Criando o Endpoint para Geração do Captcha
Após configurar o bean, crie um controller para expor um endpoint que gere o captcha.
Controller
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private static final Logger log = LoggerFactory.getLogger(AuthController.class);
@Autowired
private AuthService authService; // Supondo que você tenha um serviço para a lógica
@GetMapping("/captcha")
public void generateCaptcha(HttpServletRequest request, HttpServletResponse response) {
try {
authService.generateCaptchaImage(request, response);
} catch (IOException e) {
log.error("Falha ao gerar o captcha.", e);
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
}
Serviço (Service Layer)
import com.google.code.kaptcha.impl.DefaultKaptcha;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class AuthService {
private static final Logger log = LoggerFactory.getLogger(AuthService.class);
@Autowired
private DefaultKaptcha kaptchaProducer;
// Usando um mapa em memória para armazenar os captchas (para simplicidade)
// Em produção, considere Redis ou outro cache distribuído.
private final Map<String, String> captchaStorage = new ConcurrentHashMap<>();
public void generateCaptchaImage(HttpServletRequest request, HttpServletResponse response) throws IOException {
ByteArrayOutputStream imgOutputStream = new ByteArrayOutputStream();
String captchaText = null;
try {
// Gera o texto do captcha e o armazena (por exemplo, na sessão ou cache)
captchaText = kaptchaProducer.createText();
// Armazenar o captcha. Use um identificador único (loginKey) se aplicável.
// Aqui, para exemplo, vamos associar a uma chave arbitrária ou a sessão.
// Em um cenário real, você usaria um `loginKey` passado como parâmetro ou da sessão.
String sessionKey = "currentCaptcha"; // Chave genérica para este exemplo
request.getSession().setAttribute(sessionKey, captchaText); // Armazena na sessão HTTP
captchaStorage.put(sessionKey, captchaText); // Armazena em memória também para exemplo
log.debug("Captcha gerado para a chave '{}': {}", sessionKey, captchaText);
// Cria a imagem do captcha
BufferedImage challenge = kaptchaProducer.createImage(captchaText);
// Escreve a imagem em um buffer de saída
ImageIO.write(challenge, "jpg", imgOutputStream);
} catch (IllegalArgumentException | IOException e) {
log.error("Erro ao criar a imagem do captcha.", e);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Erro ao gerar captcha.");
return;
}
// Configura as respostas HTTP
response.setHeader("Cache-Control", "no-store, no-cache");
response.setContentType("image/jpeg");
// Escreve os bytes da imagem na resposta
try (ServletOutputStream responseOutputStream = response.getOutputStream()) {
responseOutputStream.write(imgOutputStream.toByteArray());
responseOutputStream.flush();
} catch (IOException ex) {
log.error("Erro ao escrever a resposta do captcha.", ex);
}
}
// Método de exemplo para validar o captcha (a ser chamado em outra parte da aplicação)
public boolean validateCaptcha(String providedCaptcha, String sessionKey) {
String storedCaptcha = captchaStorage.get(sessionKey);
if (storedCaptcha != null && storedCaptcha.equalsIgnoreCase(providedCaptcha)) {
captchaStorage.remove(sessionKey); // Remove após validação bem-sucedida
return true;
}
return false;
}
}
Considerações sobre Ambientes Docker
Ao implantar em ambientes Docker, especialmente com OpenJDK, você pode encontrar problemas relacionados à falta de fontes de fontes de sistema, resultando em NullPointerException durante a geração de imagens. Para resolver isso, adicione a instalação de pacotes de fontes ao seu Dockerfile:
RUN apk add --update font-adobe-100dpi ttf-dejavu fontconfig
Essa instrução garante que as fontes necessárias estejam disponíveis para o processo de geração de imagem.