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

Java 圖形驗證碼生成完整教程:從基礎繪圖到 Servlet 輸出與前端整合

     編輯:Java 相關知識

核心 API:java.awt 繪圖

  • BufferedImage:記憶體中圖片緩衝區
  • Graphics2D:繪圖上下文,支援抗鋸齒、旋轉、縮放
  • ImageIO:編碼輸出(JPEG/PNG/GIF)

階段一:最簡單驗證碼(固定文字輸出檔案)

BufferedImage img = new BufferedImage(60, 30, BufferedImage.TYPE_INT_RGB);
Graphics g = img.getGraphics();
g.drawString("Hello", 10, 20);
g.dispose();
ImageIO.write(img, "JPG", new FileOutputStream("a.jpg"));

階段二:加入干擾線、隨機顏色、隨機位置

int w = 80, h = 40;
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics g = img.getGraphics();

// 背景
g.setColor(Color.WHITE);
g.fillRect(0, 0, w, h);

// 字體
g.setFont(new Font("微軟雅黑", Font.BOLD, 18));

Random r = new Random();
for (int i = 0; i < 4; i++) {
    int digit = r.nextInt(10);
    int y = 10 + r.nextInt(20);
    g.setColor(new Color(r.nextInt(255), r.nextInt(255), r.nextInt(255)));
    g.drawString(String.valueOf(digit), 5 + i * w / 4, y);
}

// 干擾線
for (int i = 0; i < 10; i++) {
    g.setColor(new Color(r.nextInt(255), r.nextInt(255), r.nextInt(255)));
    g.drawLine(r.nextInt(w), r.nextInt(h), r.nextInt(w), r.nextInt(h));
}
g.dispose();
ImageIO.write(img, "JPG", new FileOutputStream("b.jpg"));

階段三:旋轉與縮放(更強抗識別)

Graphics2D g2d = (Graphics2D) img.getGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

for (int i = 0; i < 4; i++) {
    String ch = String.valueOf(r.nextInt(10));
    AffineTransform tx = new AffineTransform();
    tx.rotate(r.nextDouble() * 0.5 - 0.25, 5 + i * 15, h - 5); // ±14° 隨機旋轉
    // tx.scale(0.8 + r.nextDouble() * 0.4, 0.8 + r.nextDouble() * 0.4); // 隨機縮放
    g2d.setTransform(tx);
    g2d.setColor(new Color(r.nextInt(255), r.nextInt(255), r.nextInt(255)));
    g2d.drawString(ch, 2 + i * w / 4, h - 13);
}

階段四:Servlet 輸出圖片流(核心)

@WebServlet("/captcha")
public class CaptchaServlet extends HttpServlet {
    private static final int W = 100, H = 40, LEN = 4;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        resp.setContentType("image/jpeg");
        resp.setHeader("Pragma", "no-cache");
        resp.setHeader("Cache-Control", "no-store, no-cache");
        resp.setDateHeader("Expires", 0);

        BufferedImage img = new BufferedImage(W, H, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = img.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        // 背景漸變(可選)
        g2d.setPaint(new GradientPaint(0, 0, Color.WHITE, W, H, new Color(230, 240, 255)));
        g2d.fillRect(0, 0, W, H);

        Random r = new SecureRandom(); // 加密級隨機
        StringBuilder code = new StringBuilder();
        for (int i = 0; i < LEN; i++) {
            int idx = r.nextInt(36); // 0-9, a-z
            char ch = (idx < 10) ? (char)('0' + idx) : (char)('a' + idx - 10);
            code.append(ch);

            AffineTransform tx = new AffineTransform();
            tx.rotate(r.nextDouble() * 0.6 - 0.3, 10 + i * 22, H - 8);
            g2d.setTransform(tx);
            g2d.setFont(new Font("Consolas", Font.BOLD, 24));
            g2d.setColor(new Color(r.nextInt(100), r.nextInt(100), r.nextInt(100)));
            g2d.drawString(String.valueOf(ch), 10 + i * 22, H - 8);
        }

        // 干擾線
        for (int i = 0; i < 8; i++) {
            g2d.setColor(new Color(r.nextInt(200), r.nextInt(200), r.nextInt(200)));
            g2d.drawLine(r.nextInt(W), r.nextInt(H), r.nextInt(W), r.nextInt(H));
        }
        g2d.dispose();

        // 存入 Session(驗證時取出比對,忽略大小寫)
        req.getSession().setAttribute("captcha_code", code.toString().toLowerCase());

        ImageIO.write(img, "JPEG", resp.getOutputStream());
    }
}

階段五:前端整合與「看不清換一張」

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

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

階段六:後端驗證

@PostMapping("/login")
public String login(@RequestParam String username,
                    @RequestParam String password,
                    @RequestParam String captcha,
                    HttpSession session) {
    String realCode = (String) session.getAttribute("captcha_code");
    if (realCode == null || !realCode.equals(captcha.toLowerCase())) {
        return "redirect:/login?error=captcha";
    }
    // ... 正常登入邏輯
    session.removeAttribute("captcha_code"); // 用一次即失效
    return "redirect:/home";
}

進階強化方向

  1. 字符集擴充:排除易混淆字符(0/O、1/I/l、8/B),或支援中文成語驗證碼。
  2. 幹擾曲線:用 QuadCurve2D 畫貝茲曲線,比直線更難識別。
  3. 噪點:隨機畫 1-2 像素圓點。
  4. 點擊/滑動/拼圖驗證:前端 Canvas + 後端軌跡分析,體驗更好、安全性更高(如 Geetest、reCAPTCHA v3)。
  5. 分散式 Session:集群環境用 Redis 存驗證碼(Key: captcha:{sessionId},TTL 5 分鐘)。

安全提醒

  • 驗證碼必須在伺服端生成並驗證,前端不可信。
  • 使用 SecureRandom 而非 Random。
  • 設置 Cache-Control: no-store 防止代理緩存。
  • 驗證後立即從 Session/Redis 刪除,防止重放攻擊。
  • 登入介面需配合頻率限制(IP/賬號),單獨驗證碼無法防暴力破解。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved