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

SpringMVC 實戰:Servlet 生成驗證碼圖片完整教程(支持中英數混合、算術、幹擾線)

     編輯:Java 相關知識

核心類:BufferedImage

java.awt.image.BufferedImage 是內存中圖片緩衝區,支持直接繪圖、像素操作、多種圖片格式輸出(JPEG/PNG/GIF)。

完整 Servlet 實現

@WebServlet("/captcha")
public class CaptchaServlet extends HttpServlet {
    private static final int WIDTH = 130, HEIGHT = 40;
    private static final String CHARS_NUM = "0123456789";
    private static final String CHARS_LETTER = "ABCDEFGHJKLMNPQRSTUVWXYZ";
    private static final String CHARS_MIX = CHARS_NUM + CHARS_LETTER;
    private static final String CHARS_CHINESE = "的了一是我不在人有來他這上..."; // 300+ 常用字

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String type = req.getParameter("type"); // num | letter | mix | chinese | math
        if (type == null) type = "mix";

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

        // 2. 繪製背景
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, WIDTH, HEIGHT);

        // 3. 繪製邊框
        g.setColor(Color.LIGHT_GRAY);
        g.drawRect(0, 0, WIDTH - 1, HEIGHT - 1);

        // 4. 繪製幹擾線 (5~8 條)
        g.setColor(Color.GRAY);
        Random rand = new Random();
        for (int i = 0; i < 6; i++) {
            int x1 = rand.nextInt(WIDTH), y1 = rand.nextInt(HEIGHT);
            int x2 = rand.nextInt(WIDTH), y2 = rand.nextInt(HEIGHT);
            g.drawLine(x1, y1, x2, y2);
        }

        // 5. 繪製幹擾點 (可選)
        for (int i = 0; i < 30; i++) {
            g.drawOval(rand.nextInt(WIDTH), rand.nextInt(HEIGHT), 1, 1);
        }

        // 6. 生成驗證碼文本
        String code = generateCode(type, rand);
        
        // 7. 繪製文字 (隨機字體、大小、旋轉、顏色)
        Font[] fonts = { new Font("微軟雅黑", Font.BOLD, 28), new Font("Arial", Font.BOLD, 28) };
        for (int i = 0; i < code.length(); i++) {
            g.setFont(fonts[rand.nextInt(fonts.length)]);
            g.setColor(new Color(rand.nextInt(100), rand.nextInt(100), rand.nextInt(100)));
            int x = 20 + i * 28;
            int y = 28;
            double theta = (rand.nextInt(30) - 15) * Math.PI / 180; // -15°~15°
            g.rotate(theta, x, y);
            g.drawString(String.valueOf(code.charAt(i)), x, y);
            g.rotate(-theta, x, y);
        }
        g.dispose();

        // 8. 存入 Session (有效期 5 分鐘)
        HttpSession session = req.getSession();
        session.setAttribute("captcha_code", code);
        session.setMaxInactiveInterval(300);

        // 9. 輸出圖片 (禁止緩存)
        resp.setContentType("image/jpeg");
        resp.setHeader("Pragma", "No-cache");
        resp.setHeader("Cache-Control", "no-cache");
        resp.setDateHeader("Expires", 0);
        ImageIO.write(image, "JPEG", resp.getOutputStream());
    }

    private String generateCode(String type, Random rand) {
        StringBuilder sb = new StringBuilder();
        String pool = switch (type) {
            case "num" -> CHARS_NUM;
            case "letter" -> CHARS_LETTER;
            case "chinese" -> CHARS_CHINESE;
            case "math" -> { yield generateMathExpression(rand); } // 返回 "12+5=?"
            default -> CHARS_MIX;
        };
        if (!"math".equals(type)) {
            for (int i = 0; i < 4; i++) sb.append(pool.charAt(rand.nextInt(pool.length())));
        }
        return sb.toString();
    }

    private String generateMathExpression(Random rand) {
        int a = rand.nextInt(50) + 1, b = rand.nextInt(50) + 1;
        String op = rand.nextBoolean() ? "+" : "-";
        int result = "+".equals(op) ? a + b : a - b;
        // 存入 session 的是結果
        return a + op + b + "="; // 前端顯示 "12+5=",用戶輸入結果
    }

    @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { doGet(req, resp); }
}

前端集成 (JSP/HTML + JS)

<form action="/login" method="post">
    <img id="captchaImg" src="/captcha?type=mix" title="點擊刷新" style="cursor:pointer;vertical-align:middle"/>
    <input type="text" name="captcha" placeholder="驗證碼" required/>
    <button type="submit">登錄</button>
</form>

<script>
document.getElementById('captchaImg').onclick = function() {
    this.src = '/captcha?type=mix&t=' + Date.now(); // 時間戳防緩存
};
</script>

後端校驗 (Controller/Filter)

@PostMapping("/login")
public String login(@RequestParam String captcha, HttpSession session, Model model) {
    String realCode = (String) session.getAttribute("captcha_code");
    if (realCode == null || !realCode.equalsIgnoreCase(captcha.trim())) {
        model.addAttribute("msg", "驗證碼錯誤");
        return "login";
    }
    session.removeAttribute("captcha_code"); // 一次性失效
    // ... 正常登錄邏輯
    return "redirect:/home";
}

進階優化建議

  • 算術驗證碼:生成 "12+5=",Session 存結果 17,用戶體驗更好、機器識別更難。
  • 中文驗證碼:字庫取 300 高頻字,抗 OCR 能力最強,但需注意字體授權。
  • GIF 動態驗證碼:多幀疊加,徹底擊敗傳統 OCR,ImageIO.write(gif, "GIF", out) 需自定義 ImageWriter。
  • 分布式 Session:集群環境用 Redis 存驗證碼 SET captcha:sessionId code EX 300。
  • 前端滑塊/點選:更安全友好,推薦集成 Google reCAPTCHA v3 或國產 GeeTest、阿里云驗證碼 SDK。

總結

Servlet + BufferedImage 實現驗證碼輕量零依賴,適合中小項目。核心點:**隨機性(字符/顏色/旋轉/幹擾)+ 一次性 Session 存儲 + 禁緩存響應頭**。生產環境建議升級成熟驗證碼服務,防暴力破解、機器識別、重放攻擊。

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