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

Java 實戰:基於 Servlet 實現圖形驗證碼生成與校驗完整方案

     編輯:Java 程式設計入門

圖形驗證碼(CAPTCHA)是 Web 應用防範惡意註冊、暴力破解、垃圾灌水的第一道防線。本文將從原理剖析、核心算法、完整 Servlet 實現、前端集成、安全加固五個維度,手把手教你構建一個生產可用的驗證碼組件。

一、核心原理與設計要點

  1. 隨機字符生成:從預定義字符集(字母+數字)中隨機抽取 N 位字符。
  2. 圖像繪製:使用 java.awt.Graphics2D 在 BufferedImage 上繪製背景、干擾線、扭曲字符。
  3. 會話綁定:生成的驗證碼字符串存入 HttpSession,鍵名如 validateCode。
  4. 前端校驗:用戶提交表單時攜帶輸入的驗證碼,後端從 Session 取出比對(忽略大小寫),銷毀 Session 中舊碼防止重放。
  5. 防緩存:響應頭設置 Pragma: no-cache, Cache-Control: no-cache, Expires: 0,確保每次請求獲取最新圖片。

二、核心 Servlet 完整實現

package com.servlet;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.servlet.*;
import javax.servlet.http.*;

public class ValidateCodeServlet extends HttpServlet {
    // 配置參數(可在 web.xml 覆蓋)
    private int width = 120;
    private int height = 40;
    private int codeCount = 4;
    private int fontHeight;
    private int codeY;
    private int x;

    // 字符集:去除易混淆字符(0/O, 1/I, 8/B 等)
    private static final char[] CODE_SEQUENCE = {
        'A','B','C','D','E','F','G','H','J','K','M','N','P','Q','R','S','T','U','V','W','X','Y','Z',
        '2','3','4','5','6','7','8','9'
    };

    @Override
    public void init() throws ServletException {
        // 從 web.xml 讀取初始化參數
        String strWidth = getInitParameter("width");
        String strHeight = getInitParameter("height");
        String strCodeCount = getInitParameter("codeCount");
        try {
            if (strWidth != null && !strWidth.isEmpty()) width = Integer.parseInt(strWidth);
            if (strHeight != null && !strHeight.isEmpty()) height = Integer.parseInt(strHeight);
            if (strCodeCount != null && !strCodeCount.isEmpty()) codeCount = Integer.parseInt(strCodeCount);
        } catch (NumberFormatException ignored) {}
        // 計算佈局參數
        x = width / (codeCount + 1);
        fontHeight = height - 4;
        codeY = height - 6;
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        // 1. 創建畫布
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = image.createGraphics();
        Random random = new Random();

        // 2. 填充白色背景
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, width, height);

        // 3. 設置字體(抗鋸齒)
        Font font = new Font("Consolas", Font.BOLD, fontHeight);
        g.setFont(font);
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        // 4. 畫邊框
        g.setColor(Color.DARK_GRAY);
        g.drawRect(0, 0, width - 1, height - 1);

        // 5. 繪製干擾線(數量可配置)
        g.setColor(Color.LIGHT_GRAY);
        for (int i = 0; i < 30; i++) {
            int x1 = random.nextInt(width);
            int y1 = random.nextInt(height);
            int x2 = random.nextInt(12);
            int y2 = random.nextInt(12);
            g.drawLine(x1, y1, x1 + x2, y1 + y2);
        }

        // 6. 生成並繪製驗證碼字符
        StringBuilder randomCode = new StringBuilder();
        for (int i = 0; i < codeCount; i++) {
            char c = CODE_SEQUENCE[random.nextInt(CODE_SEQUENCE.length)];
            randomCode.append(c);
            // 隨機顏色(深色系,保證可讀性)
            g.setColor(new Color(random.nextInt(100), random.nextInt(100), random.nextInt(100)));
            // 隨機旋轉角度(-15° ~ +15°)增加識別難度
            double theta = (random.nextDouble() - 0.5) * Math.PI / 6;
            g.rotate(theta, (i + 1) * x, codeY);
            g.drawString(String.valueOf(c), (i + 1) * x, codeY);
            g.rotate(-theta, (i + 1) * x, codeY); // 恢復旋轉
        }

        // 7. 存入 Session
        HttpSession session = req.getSession();
        session.setAttribute("validateCode", randomCode.toString().toUpperCase());

        // 8. 禁止緩存
        resp.setHeader("Pragma", "no-cache");
        resp.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
        resp.setDateHeader("Expires", 0);
        resp.setContentType("image/jpeg");

        // 9. 輸出圖片流
        try (ServletOutputStream out = resp.getOutputStream()) {
            ImageIO.write(image, "JPEG", out);
        }
    }
}

三、web.xml 配置示例

<servlet>
    <servlet-name>ValidateCodeServlet</servlet-name>
    <servlet-class>com.servlet.ValidateCodeServlet</servlet-class>
    <init-param>
        <param-name>width</param-name>
        <param-value>130</param-value>
    </init-param>
    <init-param>
        <param-name>height</param-name>
        <param-value>45</param-value>
    </init-param>
    <init-param>
        <param-name>codeCount</param-name>
        <param-value>5</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>ValidateCodeServlet</servlet-name>
    <url-pattern>/captcha.jpg</url-pattern>
</servlet-mapping>

四、前端集成與校驗邏輯

4.1 頁面引用(支援點擊刷新)

<img id="captchaImg" src="/captcha.jpg" alt="驗證碼" style="cursor:pointer; vertical-align:middle;" />
<a href="javascript:void(0);" onclick="refreshCaptcha()">看不清?換一張</a>
<input type="text" name="captchaInput" maxlength="5" placeholder="請輸入驗證碼" />

<script>
function refreshCaptcha() {
    var img = document.getElementById('captchaImg');
    img.src = '/captcha.jpg?' + new Date().getTime(); // 附加時間戳防瀏覽器緩存
}
</script>

4.2 後端校驗(Spring MVC / 普通 Servlet 通用)

@PostMapping("/login")
public String login(@RequestParam String username,
                    @RequestParam String password,
                    @RequestParam String captchaInput,
                    HttpSession session) {
    String realCode = (String) session.getAttribute("validateCode");
    if (realCode == null || !realCode.equalsIgnoreCase(captchaInput.trim())) {
        model.addAttribute("error", "驗證碼錯誤或已過期");
        return "login";
    }
    session.removeAttribute("validateCode"); // 一次性使用,防重放
    // ... 後續業務邏輯
    return "redirect:/dashboard";
}

五、安全加固與擴展建議

加固點實現方案
字符集優化剔除 0/O, 1/I/l, 2/Z, 5/S, 8/B 等易混淆字符,提升用戶體驗
干擾噪點在繪製干擾線基礎上,增加 g.fillRect(x, y, 1, 1) 隨機噪點
字符扭曲/彈性變形使用 AffineTransform 實現字符隨機剪切、拉伸、波浪變形
中文驗證碼字符集改為常用漢字 3000 字,字體設置為「微軟雅黑」或「思源黑體」
分布式 Session集群環境下需將驗證碼存入 Redis(Key: captcha:{sessionId}, TTL: 5min)
限流防暴力同一 IP 1 分鐘內請求驗證碼圖片超過 20 次觸發限流(429 Too Many Requests)

六、總結

通過 BufferedImage + Graphics2D 繪製圖形驗證碼是 Java Web 經典且輕量的方案。關鍵在於:字符隨機性、視覺干擾強度、Session 綁定生命週期、前端刷新機制、後端一次性校驗銷毀 五大環節缺一不可。生產環境建議結合 Redis 集中存儲、IP 限流、字符扭曲算法,構建多層次防禦體系。若業務對安全性要求極高,可考慮接入成熟的第三方驗證碼服務(如 Google reCAPTCHA v3、Geetest 極驗),實現無感驗證與行為分析結合。

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