Gerando Captchas Gráficos em Spring Boot com Kaptcha

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.

Tags: spring-boot kaptcha CAPTCHA java Segurança

Publicado em 10-25 08:32