Java图形验证码案例教程

wen java案例 3

本文目录导读:

Java图形验证码案例教程

  1. 项目结构
  2. 基础版验证码生成器
  3. Servlet实现
  4. 验证逻辑
  5. 高级版本(增强安全性)
  6. 前端HTML页面
  7. 验证Servlet
  8. web.xml配置
  9. Spring/SpringBoot集成
  10. Maven依赖
  11. 使用说明

我将为您提供一个完整的Java图形验证码案例教程,包含从基础到进阶的实现方案。

项目结构

captcha-demo/
├── src/main/java/com/example/captcha/
│   ├── CaptchaGenerator.java  // 验证码生成器
│   ├── CaptchaServlet.java    // Servlet实现
│   └── VerifyCaptcha.java     // 验证逻辑
└── src/main/webapp/
    ├── index.html             // 测试页面
    └── WEB-INF/web.xml        // 配置

基础版验证码生成器

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.OutputStream;
import java.util.Random;
import javax.imageio.ImageIO;
public class CaptchaGenerator {
    // 验证码字符集
    private static final String CHAR_SET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
    private static final int WIDTH = 120;
    private static final int HEIGHT = 40;
    private static final int CODE_LENGTH = 4;
    private static final Random random = new Random();
    /**
     * 生成验证码图片
     * @param code 验证码字符串
     * @param output 输出流
     */
    public static void generateImage(String code, OutputStream output) throws Exception {
        // 创建画布
        BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = image.createGraphics();
        // 设置背景
        g2d.setColor(new Color(240, 240, 240));
        g2d.fillRect(0, 0, WIDTH, HEIGHT);
        // 绘制干扰线
        drawLines(g2d);
        // 绘制验证码
        drawCode(g2d, code);
        // 绘制干扰点
        drawNoise(g2d);
        g2d.dispose();
        // 输出图片
        ImageIO.write(image, "png", output);
    }
    /**
     * 生成随机验证码
     */
    public static String generateCode() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < CODE_LENGTH; i++) {
            sb.append(CHAR_SET.charAt(random.nextInt(CHAR_SET.length())));
        }
        return sb.toString();
    }
    /**
     * 绘制验证码字符
     */
    private static void drawCode(Graphics2D g2d, String code) {
        int fontSize = HEIGHT - 8;
        int charWidth = WIDTH / CODE_LENGTH;
        for (int i = 0; i < code.length(); i++) {
            // 随机字体
            String[] fonts = {"Arial", "Verdana", "Times New Roman", "Courier New"};
            Font font = new Font(fonts[random.nextInt(fonts.length)], 
                               random.nextBoolean() ? Font.BOLD : Font.PLAIN, 
                               fontSize);
            g2d.setFont(font);
            // 随机颜色
            g2d.setColor(new Color(random.nextInt(150), random.nextInt(150), random.nextInt(150)));
            // 随机旋转角度
            double angle = (random.nextDouble() - 0.5) * 0.4;
            g2d.rotate(angle, i * charWidth + charWidth / 2, HEIGHT / 2);
            // 绘制字符
            g2d.drawString(String.valueOf(code.charAt(i)), 
                          i * charWidth + charWidth / 4, 
                          HEIGHT / 2 + fontSize / 3);
            // 恢复旋转
            g2d.rotate(-angle, i * charWidth + charWidth / 2, HEIGHT / 2);
        }
    }
    /**
     * 绘制干扰线
     */
    private static void drawLines(Graphics2D g2d) {
        for (int i = 0; i < 5; i++) {
            g2d.setColor(new Color(random.nextInt(200), random.nextInt(200), random.nextInt(200)));
            g2d.drawLine(0, random.nextInt(HEIGHT), WIDTH, random.nextInt(HEIGHT));
        }
    }
    /**
     * 绘制干扰点
     */
    private static void drawNoise(Graphics2D g2d) {
        for (int i = 0; i < 100; i++) {
            g2d.setColor(new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)));
            g2d.fillOval(random.nextInt(WIDTH), random.nextInt(HEIGHT), 2, 2);
        }
    }
}

Servlet实现

import javax.servlet.ServletException;
import javax.servlet.http.*;
import java.io.IOException;
public class CaptchaServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        // 设置响应头,禁止缓存
        response.setHeader("Cache-Control", "no-store, no-cache");
        response.setHeader("Pragma", "no-cache");
        response.setDateHeader("Expires", 0);
        // 生成验证码
        String captchaCode = CaptchaGenerator.generateCode();
        // 存储到Session(不区分大小写,统一转大写)
        HttpSession session = request.getSession();
        session.setAttribute("captchaCode", captchaCode.toUpperCase());
        session.setMaxInactiveInterval(300); // 5分钟有效期
        // 输出图片
        response.setContentType("image/png");
        try {
            CaptchaGenerator.generateImage(captchaCode, response.getOutputStream());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        doGet(request, response);
    }
}

验证逻辑

import javax.servlet.http.HttpSession;
public class CaptchaValidator {
    /**
     * 验证验证码
     * @param session HTTP会话
     * @param userInput 用户输入的验证码
     * @return 验证结果
     */
    public static boolean validate(HttpSession session, String userInput) {
        if (session == null || userInput == null) {
            return false;
        }
        // 获取Session中的验证码
        String captchaCode = (String) session.getAttribute("captchaCode");
        if (captchaCode == null) {
            return false;
        }
        // 不区分大小写比较
        boolean isValid = captchaCode.equalsIgnoreCase(userInput.trim());
        // 验证后立即清除,防止多次使用
        if (isValid) {
            session.removeAttribute("captchaCode");
        }
        return isValid;
    }
}

高级版本(增强安全性)

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.OutputStream;
import java.util.Random;
public class AdvancedCaptchaGenerator {
    private static final int WIDTH = 150;
    private static final int HEIGHT = 50;
    private static final Random random = new Random();
    public static void generateAdvanced(String code, OutputStream output) throws Exception {
        BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = image.createGraphics();
        // 设置抗锯齿
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
                            RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, 
                            RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        // 渐变背景
        GradientPaint gradient = new GradientPaint(0, 0, 
            new Color(200, 220, 250), WIDTH, HEIGHT, new Color(250, 200, 220));
        g2d.setPaint(gradient);
        g2d.fillRect(0, 0, WIDTH, HEIGHT);
        // 字符变换
        int startX = 15;
        int charWidth = (WIDTH - 30) / code.length();
        for (int i = 0; i < code.length(); i++) {
            CharTransform(g2d, code.charAt(i), startX + i * charWidth, HEIGHT / 2);
        }
        // 高级干扰效果
        addAdvancedNoise(g2d);
        g2d.dispose();
        ImageIO.write(image, "png", output);
    }
    private static void CharTransform(Graphics2D g2d, char c, int x, int y) {
        // 随机变换参数
        int fontSize = 30 + random.nextInt(10);
        double rotation = (random.nextDouble() - 0.5) * 0.6;
        double scaleX = 0.8 + random.nextDouble() * 0.5;
        double scaleY = 0.8 + random.nextDouble() * 0.5;
        // 应用变换
        AffineTransform transform = new AffineTransform();
        transform.translate(x, y);
        transform.rotate(rotation);
        transform.scale(scaleX, scaleY);
        // 随机颜色
        float r = 0.1f + random.nextFloat() * 0.5f;
        float g = 0.1f + random.nextFloat() * 0.5f;
        float b = 0.1f + random.nextFloat() * 0.5f;
        g2d.setColor(new Color(r, g, b));
        g2d.setFont(new Font("Arial", Font.BOLD, fontSize));
        // 绘制字符
        FontMetrics metrics = g2d.getFontMetrics();
        int cx = -metrics.charWidth(c) / 2;
        int cy = (metrics.getAscent() - metrics.getDescent()) / 2;
        g2d.drawString(String.valueOf(c), cx, cy);
    }
    private static void addAdvancedNoise(Graphics2D g2d) {
        // 贝塞尔曲线
        for (int i = 0; i < 3; i++) {
            BezierCurve(g2d);
        }
        // 弧线干扰
        for (int i = 0; i < 2; i++) {
            g2d.setColor(new Color(random.nextInt(100), random.nextInt(100), random.nextInt(100)));
            int arcX = random.nextInt(WIDTH);
            int arcY = random.nextInt(HEIGHT);
            g2d.drawArc(arcX, arcY, 60, 30, 0, 180 + random.nextInt(180));
        }
        // 随机像素点
        for (int i = 0; i < 500; i++) {
            int x = random.nextInt(WIDTH);
            int y = random.nextInt(HEIGHT);
            g2d.setColor(new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)));
            g2d.fillRect(x, y, 1, 1);
        }
    }
    private static void BezierCurve(Graphics2D g2d) {
        int x1 = random.nextInt(WIDTH);
        int y1 = random.nextInt(HEIGHT);
        int x2 = random.nextInt(WIDTH);
        int y2 = random.nextInt(HEIGHT);
        g2d.setColor(new Color(random.nextInt(150), random.nextInt(150), random.nextInt(150)));
        CubicCurve2D curve = new CubicCurve2D.Double();
        curve.setCurve(x1, y1, x1 + 20, HEIGHT/2, x2 + 20, HEIGHT/2, x2, y2);
        g2d.draw(curve);
    }
}

前端HTML页面

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">验证码测试</title>
    <style>
        .captcha-container {
            display: flex;
            align-items: center;
            gap: 10px;
        }
        .captcha-img {
            cursor: pointer;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        .refresh-btn {
            padding: 5px 10px;
            background: #4CAF50;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        .result {
            margin-top: 10px;
            padding: 10px;
            border-radius: 4px;
        }
        .success { background: #d4edda; color: #155724; }
        .error { background: #f8d7da; color: #721c24; }
        .expired { background: #fff3cd; color: #856404; }
    </style>
    <script>
        // 刷新验证码
        function refreshCaptcha() {
            const img = document.getElementById('captchaImg');
            img.src = 'captcha?time=' + new Date().getTime();
        }
        // 验证验证码
        function verifyCaptcha() {
            const input = document.getElementById('captchaInput').value;
            const xhr = new XMLHttpRequest();
            xhr.open('POST', 'verify', true);
            xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
            xhr.onreadystatechange = function() {
                if (xhr.readyState === 4 && xhr.status === 200) {
                    const result = document.getElementById('result');
                    const data = JSON.parse(xhr.responseText);
                    result.className = 'result ' + data.status;
                    result.innerHTML = data.message;
                }
            };
            xhr.send('code=' + encodeURIComponent(input));
        }
        // 自动刷新
        window.onload = function() {
            refreshCaptcha();
        };
    </script>
</head>
<body>
    <h2>验证码测试</h2>
    <div class="captcha-container">
        <img id="captchaImg" class="captcha-img" 
             src="captcha" width="120" height="40" 
             alt="验证码" 
             onclick="refreshCaptcha()"
             title="点击刷新">
        <button class="refresh-btn" onclick="refreshCaptcha()">刷新</button>
    </div>
    <input type="text" id="captchaInput" placeholder="请输入验证码">
    <button onclick="verifyCaptcha()">验证</button>
    <div id="result" class="result"></div>
    <p style="color: #888; font-size: 12px;">点击图片或刷新按钮可更换验证码</p>
</body>
</html>

验证Servlet

import javax.servlet.ServletException;
import javax.servlet.http.*;
import java.io.IOException;
import java.io.PrintWriter;
public class VerifyCaptchaServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        response.setContentType("application/json;charset=UTF-8");
        String userInput = request.getParameter("code");
        PrintWriter out = response.getWriter();
        // 检查输入是否为空
        if (userInput == null || userInput.trim().isEmpty()) {
            out.write("{\"status\":\"error\",\"message\":\"请输入验证码\"}");
            return;
        }
        // 验证验证码
        HttpSession session = request.getSession(false);
        if (session == null) {
            out.write("{\"status\":\"expired\",\"message\":\"会话已过期,请刷新页面\"}");
            return;
        }
        String captchaCode = (String) session.getAttribute("captchaCode");
        if (captchaCode == null) {
            out.write("{\"status\":\"expired\",\"message\":\"验证码已过期,请刷新\"}");
            return;
        }
        // 验证结果
        if (CaptchaValidator.validate(session, userInput)) {
            out.write("{\"status\":\"success\",\"message\":\"验证成功!\"}");
        } else {
            out.write("{\"status\":\"error\",\"message\":\"验证码错误,请重试\"}");
        }
    }
}

web.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                             http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <display-name>Captcha Demo</display-name>
    <!-- 验证码生成Servlet -->
    <servlet>
        <servlet-name>captchaServlet</servlet-name>
        <servlet-class>com.example.captcha.CaptchaServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>captchaServlet</servlet-name>
        <url-pattern>/captcha</url-pattern>
    </servlet-mapping>
    <!-- 验证码验证Servlet -->
    <servlet>
        <servlet-name>verifyCaptcha</servlet-name>
        <servlet-class>com.example.captcha.VerifyCaptchaServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>verifyCaptcha</servlet-name>
        <url-pattern>/verify</url-pattern>
    </servlet-mapping>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
</web-app>

Spring/SpringBoot集成

@RestController
@RequestMapping("/api/captcha")
public class SpringCaptchaController {
    @GetMapping("/get")
    public void getCaptcha(HttpServletRequest request, HttpServletResponse response) 
            throws IOException {
        // 生成验证码
        String code = CaptchaGenerator.generateCode();
        // 存储到Session
        request.getSession().setAttribute("captchaCode", code.toUpperCase());
        // 设置响应头
        response.setContentType("image/png");
        response.setHeader("Cache-Control", "no-store");
        response.addHeader("Access-Control-Allow-Origin", "*");
        // 输出图片
        CaptchaGenerator.generateImage(code, response.getOutputStream());
    }
    @PostMapping("/verify")
    public String verify(HttpServletRequest request, 
                         @RequestParam("code") String userInput) {
        boolean isValid = CaptchaValidator.validate(request.getSession(), userInput);
        if (isValid) {
            return "{\"success\": true, \"message\": \"验证成功\"}";
        } else {
            return "{\"success\": false, \"message\": \"验证码错误\"}";
        }
    }
}

Maven依赖

<dependencies>
    <!-- 基础Servlet -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency>
    <!-- 如果使用Spring Boot -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.7.0</version>
    </dependency>
</dependencies>

使用说明

  1. 基本使用:部署项目后访问 /index.html,点击验证码图片刷新,输入验证码进行验证

  2. 安全建议

    • 验证码有效期建议设置为 5 分钟以内
    • 每次验证后立即清除验证码,防止重放攻击
    • 失败次数过多时建议加入验证码失效逻辑
    • 生产环境建议使用Google reCAPTCHA等更安全的方案
  3. 扩展建议

    • 可以添加算术验证码("3+5=?")
    • 支持中文字符验证码
    • 添加滑块验证等交互式验证码
    • 集成Redis存储验证码,支持集群部署

这个教程提供了完整的Java验证码实现方案,您可以根据实际需求进行扩展和修改。

抱歉,评论功能暂时关闭!