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();
}
}
}
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);
}
}
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>
-->
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();
}
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();
}
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();
}
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();
}
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();
}
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();
}
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:
- Start your SpringBoot project and ensure the API address is correct (e.g.,
http://localhost:8080/checkCode/line). - Open Apifox, create a new “GET” request, and enter the API address.
- 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. - Send the request multiple times to confirm the CAPTCHA changes (ensure caching is disabled).
5. Key Notes
- 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.
- 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.
- Dependency Version Compatibility: Hutool’s API may vary across versions. If errors occur, refer to the official documentation to adjust the version or code.
- 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)