首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java Web 驗證碼生成實戰:BufferedImage 繪圖、Servlet 輸出、AJAX 無刷新刷新

     編輯:Java 綜合教學

前言

驗證碼是 Web 應用防機器註冊、暴力破解、刷單的第一道防線。本文完整實現「圖片生成 → Servlet 輸出 → 前端展示 → 點擊刷新」全鏈路,涵蓋 BufferedImage 繪圖 API、Graphics 繪製幹擾線/文字、ImageIO 輸出流、Session 存儲驗證碼值、jQuery 無刷新刷新機制,提供生產可用代碼範本。

一、驗證碼圖片生成工具類

package com.util;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;

public class ImageCodeUtil {
    
    // 配置常量
    private static final int WIDTH = 100;
    private static final int HEIGHT = 35;
    private static final int CODE_LENGTH = 4;
    private static final int INTERFERE_LINES = 30;
    private static final String CHAR_POOL = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"; // 排除易混淆字符
    
    public static BufferedImage generateImage(StringBuilder codeBuffer) {
        // 1. 創建圖片緩衝區 (RGB 模式)
        BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
        Graphics g = image.getGraphics();
        
        // 2. 繪製背景
        g.setColor(getRandomLightColor());
        g.fillRect(0, 0, WIDTH, HEIGHT);
        
        // 3. 繪製幹擾線
        Random random = new Random();
        for (int i = 0; i < INTERFERE_LINES; i++) {
            g.setColor(getRandomColor(160, 200));
            int x1 = random.nextInt(WIDTH);
            int y1 = random.nextInt(HEIGHT);
            int x2 = x1 + random.nextInt(15) - 5;
            int y2 = y1 + random.nextInt(15) - 5;
            g.drawLine(x1, y1, x2, y2);
        }
        
        // 4. 繪製驗證碼字符
        g.setFont(new Font("Fixedsys", Font.BOLD, 24)); // 等寬字體,難識別
        g.setColor(getRandomDarkColor());
        
        StringBuilder code = new StringBuilder();
        for (int i = 0; i < CODE_LENGTH; i++) {
            char c = CHAR_POOL.charAt(random.nextInt(CHAR_POOL.length()));
            code.append(c);
            // 字符隨機旋轉、位置微調,增加識別難度
            int x = 20 * i + 10 + random.nextInt(5);
            int y = 24 + random.nextInt(5);
            g.drawString(String.valueOf(c), x, y);
        }
        
        // 5. 釋放資源
        g.dispose();
        
        // 6. 返回驗證碼值 (供 Session 存儲)
        if (codeBuffer != null) {
            codeBuffer.append(code);
        }
        
        return image;
    }
    
    // 輔助顏色方法
    private static Color getRandomLightColor() {
        Random r = new Random();
        return new Color(200 + r.nextInt(55), 200 + r.nextInt(55), 200 + r.nextInt(55));
    }
    
    private static Color getRandomColor(int min, int max) {
        Random r = new Random();
        int rVal = min + r.nextInt(max - min);
        int gVal = min + r.nextInt(max - min);
        int bVal = min + r.nextInt(max - min);
        return new Color(rVal, gVal, bVal);
    }
    
    private static Color getRandomDarkColor() {
        Random r = new Random();
        return new Color(r.nextInt(100), r.nextInt(100), r.nextInt(100));
    }
}

二、驗證碼 Servlet 輸出

package com.controller;

import com.util.ImageCodeUtil;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.awt.image.BufferedImage;
import java.io.IOException;

@WebServlet("/captcha")
public class CaptchaServlet extends HttpServlet {
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        
        // 1. 設置響應頭:禁止緩存、指定圖片類型
        resp.setHeader("Pragma", "No-cache");
        resp.setHeader("Cache-Control", "no-cache");
        resp.setDateHeader("Expires", 0);
        resp.setContentType("image/jpeg");
        
        // 2. 生成驗證碼圖片並獲取驗證碼值
        StringBuilder codeBuffer = new StringBuilder();
        BufferedImage image = ImageCodeUtil.generateImage(codeBuffer);
        String captchaCode = codeBuffer.toString();
        
        // 3. 存入 Session (大小寫不敏感驗證建議統一轉大寫)
        HttpSession session = req.getSession();
        session.setAttribute("captchaCode", captchaCode.toUpperCase());
        
        // 4. 輸出圖片流
        ImageIO.write(image, "JPEG", resp.getOutputStream());
    }
}

三、前端頁面整合

1. JSP/HTML 表單

<%@ page contentType="text/html;charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <title>登錄頁面</title>
    <script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
    <style>
        .captcha-img { cursor: pointer; vertical-align: middle; border: 1px solid #ddd; }
        .captcha-input { width: 80px; margin-left: 5px; vertical-align: middle; }
    </style>
</head>
<body>
    <form id="loginForm" action="login" method="post">
        <div>
            <label>用戶名:</label>
            <input type="text" name="username" required/>
        </div>
        <div>
            <label>密碼:</label>
            <input type="password" name="password" required/>
        </div>
        <div>
            <label>驗證碼:</label>
            <input type="text" name="captcha" class="captcha-input" maxlength="4" required/>
            <img id="captchaImg" class="captcha-img" src="captcha" alt="點擊刷新驗證碼" title="點擊刷新驗證碼"/>
        </div>
        <button type="submit">登錄</button>
    </form>

    <script>
        // 點擊圖片刷新驗證碼 (加時間戳防瀏覽器緩存)
        $("#captchaImg").click(function() {
            $(this).attr("src", "captcha?t=" + new Date().getTime());
        });
    </script>
</body>
</html>

2. 登錄處理 Servlet (驗證邏輯)

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=UTF-8");
        
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        String captchaInput = req.getParameter("captcha");
        
        // 1. 獲取 Session 中驗證碼
        HttpSession session = req.getSession(false);
        String captchaCode = (session != null) ? (String) session.getAttribute("captchaCode") : null;
        
        // 2. 驗證碼校驗 (不區分大小寫)
        if (captchaCode == null || !captchaCode.equalsIgnoreCase(captchaInput)) {
            req.setAttribute("error", "驗證碼錯誤或已過期");
            req.getRequestDispatcher("login.jsp").forward(req, resp);
            return;
        }
        
        // 3. 驗證通過,移除驗證碼 (一次性使用)
        session.removeAttribute("captchaCode");
        
        // 4. 業務邏輯:用戶名密碼驗證 (此處省略,接入 UserService)
        if ("admin".equals(username) && "123456".equals(password)) {
            session.setAttribute("loginUser", username);
            resp.sendRedirect("index.jsp");
        } else {
            req.setAttribute("error", "用戶名或密碼錯誤");
            req.getRequestDispatcher("login.jsp").forward(req, resp);
        }
    }
}

四、生產環境增強方案

1. 驗證碼安全性加強

維度增強措施
字符池排除 0/O、1/I/l、8/B、5/S 等易混淆字符
幹擾元素幹擾線 + 幹擾圓點 + 幹擾弧線 + 前景幹擾字符
字體變形隨機字體、隨機大小、隨機旋轉角度 (-30°~30°)、隨機平移
顏色空間背景淺色系、前景深色系、幹擾中間色系,確保對比度
一次性失效驗證後立即 session.removeAttribute(),防止重放攻擊
頻率限制同 IP/Session 短時間請求驗證碼次數限制 (如 10次/分鐘)
有效期控制Session 設置 maxInactiveInterval (如 5 分鐘) 或單獨存儲時間戳校驗

2. 高性能優化

  • 對象池復用:BufferedImage/Graphics 對象池化,減少 GC 壓力
  • 異步生成:預生成驗證碼圖片隊列,請求時直接取用
  • CDN 緩存:靜態資源 (字體文件、背景圖) 走 CDN
  • 響應頭優化:Content-Length、ETag、Cache-Control: no-store, must-revalidate

3. 現代化替代方案

  • 滑動拼圖驗證:用戶體驗更好、機器識別更難 (需前端 Canvas + 後端軌跡分析)
  • 點選文字/圖標驗證:語義識別門檻高
  • 行為分析驗證:無感驗證,基於鼠標軌跡、點擊節奏、設備指紋 (如 Google reCAPTCHA v3、阿里云 驗證碼、極驗)
  • 短信/郵件驗證碼:高安全場景雙因子認證 (2FA)

五、效果展示

驗證碼圖片示例1 驗證碼圖片示例2

六、結語

驗證碼生成雖屬基礎功能,但涉及「圖形學繪製、Web 流輸出、Session 狀態管理、前端交互、安全對抗」多維技能。掌握 BufferedImage + Graphics 核心繪圖鏈路,理解 ImageIO.write 輸出機制,構建完整「生成→輸出→驗證→失效」閉環,並針對機器識別攻擊持續迭代幹擾策略,是構建安全 Web 應用的必修課。建議團隊封裝通用 CaptchaService,支持多種驗證碼類型 (圖片/滑動/點選/無感),統一接入網關層,實現「業務零感知安全防護」。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved