防止機器人惡意註冊、暴力破解、刷單投票,圖片驗證碼 (Captcha) 是最基礎且有效的手段。本文將使用 Java 原生 java.awt 繪圖 API + Servlet,從零實作一個包含「隨機字符、干擾線、背景噪點、Session 儲存、前端驗證」的完整驗證碼功能,無需第三方庫,適合教學與輕量級專案。
BufferedImage (寬 120px、高 30px、TYPE_INT_RGB)ImageIO.write(image, "jpg", response.getOutputStream())request.getSession().setAttribute("captcha", code) 供後續驗證package com.example.captcha; import java.awt.*; import java.awt.image.BufferedImage; import java.io.IOException; import java.util.Random; import javax.imageio.ImageIO; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.*; @WebServlet(urlPatterns = "/captcha.jpg", asyncSupported = true) public class CaptchaServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final String CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // 排除易混淆字符 private static final int WIDTH = 120, HEIGHT = 30, CODE_LENGTH = 4, LINE_COUNT = 50; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 建立圖片緩衝區 BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); Graphics g = image.getGraphics(); // 2. 背景與邊框 g.setColor(new Color(255, 255, 200)); // 淡黃 g.fillRect(0, 0, WIDTH, HEIGHT); g.setColor(Color.BLUE); g.drawRect(0, 0, WIDTH - 1, HEIGHT - 1); // 3. 繪製隨機字符 Random random = new Random(); StringBuilder code = new StringBuilder(); g.setFont(new Font("微軟雅黑", Font.BOLD, 22)); g.setColor(Color.RED); int x = 15, y = 22; for (int i = 0; i < CODE_LENGTH; i++) { char c = CHARS.charAt(random.nextInt(CHARS.length())); code.append(c); g.drawString(String.valueOf(c), x, y); x += 22; // 字符間距 } // 4. 干擾線 g.setColor(Color.LIGHT_GRAY); for (int i = 0; i < LINE_COUNT; i++) { 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. 釋放繪圖資源 g.dispose(); // 6. 儲存驗證碼到 Session (忽略大小寫驗證時統一轉大寫) req.getSession().setAttribute("captcha", code.toString().toUpperCase()); // 7. 輸出圖片 resp.setContentType("image/jpeg"); resp.setHeader("Pragma", "no-cache"); resp.setHeader("Cache-Control", "no-store"); resp.setDateHeader("Expires", 0); ImageIO.write(image, "jpg", resp.getOutputStream()); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } } <form id="loginForm" action="login.do" method="post"> <div> <label>帳號:</label> <input name="username" required/> </div> <div> <label>密碼:</label> <input name="password" type="password" required/> </div> <div> <label>驗證碼:</label> <input name="captcha" id="captchaInput" maxlength="4" required/> <img id="captchaImg" src="captcha.jpg" alt="點擊換圖" style="cursor:pointer;vertical-align:middle;height:30px;"/> </div> <button type="submit">登入</button> </form> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script> // 點擊圖片換新驗證碼 $('#captchaImg').click(function() { $(this).attr('src', 'captcha.jpg?' + new Date().getTime()); }); // 表單提交前簡易驗證 (可選) $('#loginForm').submit(function() { var input = $('#captchaInput').val().toUpperCase(); if (input.length !== 4) { alert('請輸入 4 位驗證碼'); return false; } }); </script> @PostMapping("/login.do") @ResponseBody public Result login(@RequestParam String username, @RequestParam String password, @RequestParam String captcha, HttpSession session) { String realCaptcha = (String) session.getAttribute("captcha"); if (realCaptcha == null || !realCaptcha.equalsIgnoreCase(captcha)) { return Result.error("驗證碼錯誤或已過期"); } session.removeAttribute("captcha"); // 一次性使用 // 這裡接真實帳密驗證邏輯... return Result.ok("登入成功"); } | 方向 | 實作建議 |
|---|---|
| 字符扭曲/旋轉 | 使用 AffineTransform 對每字符隨機旋轉 ±15°、平移 |
| 背景噪點 | 隨機繪製 200+ 個單像素點,顏色隨機 |
| 字體隨機化 | 準備 5-10 支字體檔,隨機選取 |
| 儲存改用 Redis | 分散式部署時 Session 不共享,改用 Redis + UUID Key,TTL 5 分鐘 |
| 引入成熟庫 | kaptcha、simple-captcha、hutool-captcha,功能豐富、維護省心 |
| 無障礙支援 | 提供語音驗證碼按鈕 (TTS 朗讀),符合無障礙規範 |
ImageIO.write 是否拋異常、Response 是否已被其他 Filter 提交。captcha 屬性名拼寫正確、未在 Filter 中提前清除 Session。yum install fontconfig dejavu-sans-fonts 或複製 msyh.ttc 到 $JAVA_HOME/jre/lib/fonts。Google Guava Cache 或 Redis 快取生成圖片 (Key 為隨機碼),驗證時比對快取。本文實作了一個「輕量、可控、零依賴」的 Java 圖片驗證碼,涵蓋繪圖核心、Session 綁定、前後端交互全流程。理解原理後,生產環境建議直接採用成熟庫 (如 Hutool Captcha) 並配合 Redis 分散式儲存,兼顧安全性、可用性與維護效率。保護用戶帳號安全,從一個小小的驗證碼開始。