DEV Community

Cover image for SpringBoot CAPTCHA Implementation Tutorial: From Custom Development to Hutool Ut
Tiger Smith
Tiger Smith

Posted on

SpringBoot CAPTCHA Implementation Tutorial: From Custom Development to Hutool Ut

This article details two implementation solutions for graphic CAPTCHAs in SpringBoot projects, including handwritten custom CAPTCHA utility classes and rapid integration of four types of CAPTCHAs (line-interfered, circle-interfered, distorted, and GIF) using the Hutool utility library. Complete code examples and API testing steps are provided to help developers address human-machine verification needs in scenarios such as login and registration.

Source of the article:Dev Resource Hub

1. Why Do We Need Graphic CAPTCHAs?

In user interaction scenarios like login, registration, and password reset, graphic CAPTCHAs are a critical defense against malicious scripts and brute-force attacks. By combining random characters with interfering elements, they ensure operations are performed by real users rather than automated programs.

There are two traditional implementation methods: developing CAPTCHA generation logic manually, or quickly integrating with mature utility libraries. Below is a detailed breakdown of both solutions—you can choose the one that best fits your project requirements.

2. Solution 1: Develop a Custom CAPTCHA Utility Class

If you need highly customized CAPTCHA styles (e.g., specific fonts or interference line density), you can develop a utility class manually. Here are the complete implementation steps.

2.1 Create a CAPTCHA Utility Class

Under the util package of your SpringBoot project, create a Code class. Its core logic includes generating random characters, drawing interference lines, outputting images to the response stream, and storing the CAPTCHA in the Session for subsequent verification.

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;

public class Code {
    // Key for storing CAPTCHA in Session
    public static final String RANDOMCODEKEY = "ValidateCode";
    // Random number generator
    private final Random random = new Random();
    // CAPTCHA character set (numbers + uppercase letters)
    private final String randomString = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    // CAPTCHA image dimensions and interference line settings
    private int width = 80;
    private int height = 26;
    private int lineSize = 40;
    private int stringNum = 4;

    /**
     * Get CAPTCHA font (fixed as Fixedsys, size 18)
     */
    private Font getFont() {return new Font("Fixedsys", Font.CENTER_BASELINE, 18);
    }

    /**
     * Generate random color (avoids overly dark or light shades)
     */
    private Color getRandColor(int fc, int bc) {if (fc > 255) fc = 255;
        if (bc > 255) bc = 255;
        int r = fc + random.nextInt(bc - fc - 16);
        int g = fc + random.nextInt(bc - fc - 14);
        int b = fc + random.nextInt(bc - fc - 18);
        return new Color(r, g, b);
    }

    /**
     * Draw interference lines (random positions and lengths)
     */
    private void drawLine(Graphics g) {int x = random.nextInt(width);
        int y = random.nextInt(height);
        int xl = random.nextInt(13);
        int yl = random.nextInt(15);
        g.drawLine(x, y, x + xl, y + yl);
    }

    /**
     * Draw random characters (random colors with slight offsets)
     */
    private String drawString(Graphics g, String randomStr, int i) {g.setFont(getFont());
        g.setColor(new Color(random.nextInt(101), random.nextInt(111), random.nextInt(121)));
        String charStr = String.valueOf(randomString.charAt(random.nextInt(randomString.length())));
        randomStr += charStr;
        // Slight character offset to increase recognition difficulty
        g.translate(random.nextInt(3), random.nextInt(3));
        g.drawString(charStr, 13 * i, 16);
        return randomStr;
    }

    /**
     * Core method: Generate CAPTCHA and output to response stream
     */
    public void getValidateCode(HttpServletRequest request, HttpServletResponse response) {HttpSession session = request.getSession();
        // 1. Create image buffer
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
        Graphics g = image.getGraphics();

        // 2. Draw image background
        g.fillRect(0, 0, width, height);
        g.setFont(new Font("Times New Roman", Font.ROMAN_BASELINE, 18));
        g.setColor(getRandColor(110, 133));

        // 3. Draw interference lines
        for (int i = 0; i <= lineSize; i++) {drawLine(g);
        }

        // 4. Draw CAPTCHA characters
        String randomStr = "";
        for (int i = 1; i <= stringNum; i++) {randomStr = drawString(g, randomStr, i);
        }

        // 5. Store CAPTCHA in Session (overwrite old value)
        session.removeAttribute(RANDOMCODEKEY);
        session.setAttribute(RANDOMCODEKEY, randomStr);

        // 6. Release resources and output image
        g.dispose();
        try {ImageIO.write(image, "JPEG", response.getOutputStream());
        } catch (Exception e) {e.printStackTrace();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

2.2 Call the Utility Class in Controller

Create a CaptchaController and define the /checkCode2 API. Set the response format to image and disable browser caching (to prevent repeated CAPTCHA loading).

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@RestController
public class CaptchaController {

    /**
     * Custom CAPTCHA API
     */
    @GetMapping("/checkCode2")
    public void checkCode2(HttpServletRequest request, HttpServletResponse response) {
        // 1. Set response format to JPEG image
        response.setContentType("image/jpeg");
        // 2. Disable browser caching (critical: prevent CAPTCHA reuse)
        response.setDateHeader("Expires", 0);
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Cache-Control", "no-cache");
        // 3. Call utility class to generate CAPTCHA
        Code code = new Code();
        code.getValidateCode(request, response);
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Solution 2: Rapid CAPTCHA Integration with Hutool

Hutool is a popular utility library in the Java ecosystem. Its hutool-captcha module encapsulates four types of CAPTCHAs, eliminating the need for redundant development. This approach is recommended for most projects.

3.1 Add Hutool Dependency

Add the dependency to your pom.xml (for Maven). For Gradle projects, refer to the Hutool official documentation for configuration adjustments.

<!-- Option 1: Only import the graphic CAPTCHA module (lightweight) -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-captcha</artifactId>
    <version>5.8.6</version>
</dependency>

<!-- Option 2: Import all Hutool modules (for scenarios needing other utilities) -->
<!--
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.16</version>
</dependency>
-->
Enter fullscreen mode Exit fullscreen mode

3.2 Implementation Examples for Four CAPTCHA Types

Hutool provides LineCaptcha (line-interfered), CircleCaptcha (circle-interfered), ShearCaptcha (distorted), and GifCaptcha (animated GIF). The API calling logic is similar—only the CAPTCHA creation method needs to be modified.

3.2.1 Line-Interfered CAPTCHA (LineCaptcha)

The most basic CAPTCHA type, using line interference to enhance security.

@GetMapping("/checkCode/line")
public void lineCaptcha(HttpServletResponse response) throws IOException {
    // 1. Create line-interfered CAPTCHA: 130px width, 38px height, 5 characters, 5 interference lines
    LineCaptcha captcha = CaptchaUtil.createLineCaptcha(130, 38, 5, 5);
    // 2. Disable caching (same as custom solution)
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Cache-Control", "no-cache");
    response.setDateHeader("Expires", 0);
    response.setContentType("image/jpeg");
    // 3. Output CAPTCHA to response stream
    captcha.write(response.getOutputStream());
    // 4. Close stream (prevent resource leaks)
    response.getOutputStream().close();
}
Enter fullscreen mode Exit fullscreen mode

3.2.2 Circle-Interfered CAPTCHA (CircleCaptcha)

Uses circular dots instead of lines for a more user-friendly visual effect.

@GetMapping("/checkCode/circle")
public void circleCaptcha(HttpServletResponse response) throws IOException {
    // Create circle-interfered CAPTCHA: 130px width, 38px height, 5 characters, 20 interference circles
    CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(130, 38, 5, 20);
    // Subsequent caching disabling and stream output logic is identical to line CAPTCHA
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Cache-Control", "no-cache");
    response.setDateHeader("Expires", 0);
    response.setContentType("image/jpeg");
    captcha.write(response.getOutputStream());
    response.getOutputStream().close();
}
Enter fullscreen mode Exit fullscreen mode

3.2.3 Distorted CAPTCHA (ShearCaptcha)

Characters are distorted to improve security, suitable for scenarios requiring high verification strength.

@GetMapping("/checkCode/shear")
public void shearCaptcha(HttpServletResponse response) throws IOException {
    // Create distorted CAPTCHA: 130px width, 38px height, 5 characters, 5 interference lines
    ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(130, 38, 5, 5);
    // Caching disabling and output logic is the same as above
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Cache-Control", "no-cache");
    response.setDateHeader("Expires", 0);
    response.setContentType("image/jpeg");
    captcha.write(response.getOutputStream());
    response.getOutputStream().close();
}
Enter fullscreen mode Exit fullscreen mode

3.2.4 Animated GIF CAPTCHA (GifCaptcha)

Animated image CAPTCHAs effectively defend against simple image recognition scripts.

@GetMapping("/checkCode/gif")
public void gifCaptcha(HttpServletResponse response) throws IOException {// Create GIF CAPTCHA: 130px width, 38px height, 5 characters (no interference line parameter)
    GifCaptcha captcha = CaptchaUtil.createGifCaptcha(130, 38, 5);
    // Note: The response type for GIF CAPTCHAs remains image/jpeg; browsers automatically recognize it
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Cache-Control", "no-cache");
    response.setDateHeader("Expires", 0);
    response.setContentType("image/jpeg");
    captcha.write(response.getOutputStream());
    response.getOutputStream().close();
}
Enter fullscreen mode Exit fullscreen mode

3.3 Customize CAPTCHA Content

Hutool supports custom CAPTCHA character sets, such as numeric-only, letter-only, or even arithmetic CAPTCHAs.

Example 1: Numeric-Only CAPTCHA

@GetMapping("/checkCode/number")
public void numberCaptcha(HttpServletResponse response) throws IOException {
    // 1. Custom character generator: use only 0-9, generate 4 characters
    RandomGenerator numberGenerator = new RandomGenerator("0123456789", 4);
    // 2. Create line CAPTCHA and set custom generator
    LineCaptcha captcha = CaptchaUtil.createLineCaptcha(200, 100);
    captcha.setGenerator(numberGenerator);
    // 3. Regenerate CAPTCHA (must call this; otherwise default characters are used)
    captcha.createCode();
    // 4. Output CAPTCHA
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Cache-Control", "no-cache");
    response.setDateHeader("Expires", 0);
    response.setContentType("image/jpeg");
    captcha.write(response.getOutputStream());
    response.getOutputStream().close();
}
Enter fullscreen mode Exit fullscreen mode

Example 2: Arithmetic CAPTCHA

Use MathGenerator to generate expressions like “3+5=?”. Users need to enter the result for verification (note: the Session stores the calculation result, not the displayed characters).

@GetMapping("/checkCode/math")
public void mathCaptcha(HttpServletResponse response, HttpSession session) throws IOException {
    // 1. Create distorted CAPTCHA
    ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(200, 45, 4, 4);
    // 2. Set arithmetic expression generator
    captcha.setGenerator(new MathGenerator());
    // 3. Regenerate CAPTCHA
    captcha.createCode();
    // 4. Store calculation result in Session (subsequent verification compares user input with this value)
    session.setAttribute("MathCode", captcha.getCode());
    // 5. Output CAPTCHA
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Cache-Control", "no-cache");
    response.setDateHeader("Expires", 0);
    response.setContentType("image/jpeg");
    captcha.write(response.getOutputStream());
    response.getOutputStream().close();
}
Enter fullscreen mode Exit fullscreen mode

4. API Testing: Verify CAPTCHAs with Apifox

Regardless of the solution, you need to test if the API returns CAPTCHAs correctly. Using Apifox, follow these steps:

  1. Start your SpringBoot project and ensure the API address is correct (e.g., http://localhost:8080/checkCode/line).
  2. Open Apifox, create a new “GET” request, and enter the API address.
  3. Click the “Send” button. If the response body displays an image, the API works normally. If garbled text appears, check if response.setContentType("image/jpeg") is configured.
  4. Send the request multiple times to confirm the CAPTCHA changes (ensure caching is disabled).

5. Key Notes

  1. Session Storage and Verification: When generating a CAPTCHA, store the characters (or calculation result) in the Session. After the user submits the form, compare the input with the Session value. Clear the Session after successful verification to prevent reuse.
  2. Image Size Adaptation: Adjust the CAPTCHA dimensions based on your frontend layout. It is recommended to use a width of at least 120px and height of at least 30px to ensure clear recognition.
  3. Dependency Version Compatibility: Hutool’s API may vary across versions. If errors occur, refer to the official documentation to adjust the version or code.
  4. Security Enhancement: For high-security scenarios, combine CAPTCHAs with IP restrictions (rate-limiting for frequent CAPTCHA requests from the same IP) or SMS verification to further improve defense capabilities.

Top comments (0)