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

圖片驗證碼 (CAPTCHA) 完整實作指南:BufferedImage 繪製、Session 驗證、噪點干擾與無障礙替代方案

     編輯:Java 綜合教學

前言

圖片驗證碼是防止機器人暴力破解、垃圾註冊的第一道防線。本文基於 **Java Servlet + BufferedImage** 實作完整驗證碼生成與驗證流程,並提供進階干擾策略與現代替代方案。

核心類別

  • `BufferedImage`:記憶體中圖片緩衝區,支援繪圖操作。
  • `Graphics2D`:2D 繪圖上下文,繪製文字、線條、形狀。
  • `ImageIO`:圖片編解碼,輸出 JPEG/PNG 至 `OutputStream`。
  • `HttpSession`:儲存正確驗證碼,供後續驗證比對。

完整 Servlet 實作 (CaptchaServlet.java)

@WebServlet("/captcha")
public class CaptchaServlet extends HttpServlet {

    private static final int WIDTH = 120;
    private static final int HEIGHT = 40;
    private static final int CODE_LENGTH = 4;
    private static final String CHARS = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"; // 排除易混淆字元
    private static final Random RANDOM = new Random();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 1. 設定回應標頭:禁止快取、指定圖片類型
        resp.setHeader("Pragma", "No-cache");
        resp.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
        resp.setDateHeader("Expires", 0);
        resp.setContentType("image/jpeg");

        // 2. 建立圖片緩衝區
        BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = image.createGraphics();

        // 3. 繪製背景 (隨機淺色)
        g.setColor(getRandomColor(200, 250));
        g.fillRect(0, 0, WIDTH, HEIGHT);

        // 4. 繪製干擾線 (10-20 條)
        for (int i = 0; i < RANDOM.nextInt(11) + 10; i++) {
            g.setColor(getRandomColor(100, 200));
            int x1 = RANDOM.nextInt(WIDTH);
            int y1 = RANDOM.nextInt(HEIGHT);
            int x2 = RANDOM.nextInt(WIDTH);
            int y2 = RANDOM.nextInt(HEIGHT);
            g.drawLine(x1, y1, x2, y2);
        }

        // 5. 繪製干擾點 (100-200 個)
        for (int i = 0; i < RANDOM.nextInt(101) + 100; i++) {
            g.setColor(getRandomColor(100, 250));
            int x = RANDOM.nextInt(WIDTH);
            int y = RANDOM.nextInt(HEIGHT);
            g.drawOval(x, y, 1, 1);
        }

        // 6. 產生隨機驗證碼並繪製文字
        StringBuilder code = new StringBuilder();
        Font font = new Font("Arial", Font.BOLD | Font.ITALIC, 28);
        g.setFont(font);
        FontMetrics fm = g.getFontMetrics();
        int charWidth = (WIDTH - 10) / CODE_LENGTH;

        for (int i = 0; i < CODE_LENGTH; i++) {
            char c = CHARS.charAt(RANDOM.nextInt(CHARS.length()));
            code.append(c);
            g.setColor(getRandomColor(20, 100)); // 深色文字
            int x = 5 + i * charWidth + RANDOM.nextInt(5);
            int y = HEIGHT / 2 + fm.getAscent() / 2 - 2;
            g.drawString(String.valueOf(c), x, y);
        }

        // 7. 釋放繪圖資源
        g.dispose();

        // 8. 存入 Session (key 建議唯一化,如 captcha_{sessionId})
        req.getSession().setAttribute("captcha_code", code.toString().toLowerCase()); // 忽略大小寫

        // 9. 輸出圖片
        ImageIO.write(image, "JPEG", resp.getOutputStream());
    }

    private Color getRandomColor(int min, int max) {
        int r = min + RANDOM.nextInt(max - min);
        int g = min + RANDOM.nextInt(max - min);
        int b = min + RANDOM.nextInt(max - min);
        return new Color(r, g, b);
    }
}

前端整合 (login.jsp)

<form action="loginHandler" method="post">
    <div>
        <label>用戶名:</label>
        <input name="username" required>
    </div>
    <div>
        <label>密碼:</label>
        <input type="password" name="password" required>
    </div>
    <div>
        <label>驗證碼:</label>
        <input name="captcha" maxlength="4" required>
        <img id="captchaImg" src="/captcha" alt="點擊更換驗證碼" style="cursor:pointer; vertical-align:middle;" title="點擊更換">
    </div>
    <button type="submit">登入</button>
</form>

<script>
document.getElementById('captchaImg').onclick = function() {
    this.src = '/captcha?' + Date.now(); // 加時間戳防快取
};
</script>

後端驗證 (LoginHandler.java)

@WebServlet("/loginHandler")
public class LoginHandler extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        String inputCaptcha = req.getParameter("captcha");

        // 1. 取得 Session 中驗證碼
        String realCaptcha = (String) req.getSession().getAttribute("captcha_code");

        // 2. 基本校驗
        if (realCaptcha == null || inputCaptcha == null || 
            !realCaptcha.equalsIgnoreCase(inputCaptcha.trim())) {
            resp.sendRedirect("login.jsp?error=captcha");
            return;
        }

        // 3. 驗證碼一次性使用,驗證後立即移除
        req.getSession().removeAttribute("captcha_code");

        // 4. 進行用戶名/密碼驗證 (略)
        // boolean ok = userService.login(username, password);
        // if (ok) { req.getSession().setAttribute("user", username); resp.sendRedirect("index.jsp"); }
        // else { resp.sendRedirect("login.jsp?error=auth"); }
    }
}

進階強化策略

策略實作方式效果
字體隨機化準備多種字體檔,隨機選擇 `new Font(fonts[random], Font.BOLD, size)`增加 OCR 識別難度
文字扭曲/旋轉`AffineTransform` 旋轉、切變每個字元顯著降低機器識別率
中文驗證碼字庫 `String CHARS = "的一是不了人..."`,字體需支援中文 (如微軟雅黑)針對中文環境更友善
算術/邏輯驗證碼顯示 `12 + 5 = ?`、`選擇水果圖片`語義理解門檻高
Session 分離存儲Redis 存儲 `captcha:{uuid}`,前端攜帶 UUID,無狀態擴展叢集環境共享、支援分布式
速率限制同一 IP/Session 分鐘請求上限 (Guava RateLimiter / Redis 計數)防暴力刷新

現代替代方案:無感驗證 / 行為分析

  • Google reCAPTCHA v3:分數制,無需用戶互動,後端驗證分數閾值。
  • hCaptcha:隱私優先、支援中文、可換圖片分類任務。
  • 滑塊/拼圖驗證:前端 Canvas 交互,後端驗證軌跡/拼合座標。
  • 設備指紋 + 風控引擎:綜合 IP、UA、行為模式、信譽庫判斷風險。

無障礙設計 (a11y) 必做

  • 提供 **音頻驗證碼** 選項 (TTS 朗讀驗證碼),給視障用戶。
  • `驗證碼,點擊更換` 提供替代文字。
  • 確保顏色對比度符合 WCAG AA (4.5:1)。
  • 錯誤提示清晰:`驗證碼錯誤,請重新輸入` 而非通用 `登入失敗`。

結語

傳統圖片驗證碼以 **BufferedImage + Session** 實作簡單有效,但面對 AI OCR 已形同虛設。生產系統建議**疊加多層防護**:
1. 前端行為分析 (滑鼠軌跡、鍵盤節奏) → 2. 無感驗證 (reCAPTCHA v3) → 3. 高風險觸發圖形/滑塊驗證 → 4. 後端速率限制 + 帳號鎖定。
本文提供的 Servlet 實作適合教學、內部系統、低風險場景;對外開放系統請採用成熟風控服務。

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