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

Java 驗證碼生成實戰:圖片干擾線與中文漢字驗證碼完整代碼

     編輯:Java 相關知識

核心技術點

  • BufferedImage 內存圖片畫布
  • Graphics2D 繪製文字、線條、形狀
  • ImageIO.write() 輸出 PNG/JPEG 流
  • Unicode 範圍生成常用漢字
  • Session 存儲驗證碼值供後端校驗

一、經典圖片驗證碼(英數字 + 干擾線)

public class CaptchaUtil {
    private static final Random RANDOM = new Random();
    private static final String CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // 排除易混淆字符
    private static final int WIDTH = 120, HEIGHT = 40;
    private static final int CODE_LENGTH = 4;
    
    public static BufferedImage generate(StringBuilder codeHolder) {
        BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = image.createGraphics();
        
        // 1. 背景
        g.setColor(randomColor(200, 250));
        g.fillRect(0, 0, WIDTH, HEIGHT);
        
        // 2. 干擾線
        g.setColor(randomColor(160, 200));
        for (int i = 0; i < 20; 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);
        }
        
        // 3. 驗證碼文字
        g.setFont(new Font("微軟雅黑", Font.BOLD, 28));
        StringBuilder code = new StringBuilder();
        for (int i = 0; i < CODE_LENGTH; i++) {
            char c = CHARS.charAt(RANDOM.nextInt(CHARS.length()));
            code.append(c);
            g.setColor(randomColor(20, 130));
            int x = 15 + i * 25;
            int y = HEIGHT - 8;
            g.drawString(String.valueOf(c), x, y);
        }
        
        // 4. 干擾噪點
        g.setColor(randomColor(160, 200));
        for (int i = 0; i < 30; i++) {
            g.drawRect(RANDOM.nextInt(WIDTH), RANDOM.nextInt(HEIGHT), 1, 1);
        }
        
        g.dispose();
        codeHolder.setLength(0);
        codeHolder.append(code);
        return image;
    }
    
    private static Color randomColor(int min, int max) {
        return new Color(RANDOM.nextInt(max - min) + min,
                         RANDOM.nextInt(max - min) + min,
                         RANDOM.nextInt(max - min) + min);
    }
    
    // 輸出到 OutputStream (Servlet 直接寫響應)
    public static void writeToStream(OutputStream os, StringBuilder codeHolder) throws IOException {
        BufferedImage img = generate(codeHolder);
        ImageIO.write(img, "PNG", os);
    }
}

Servlet 整合範例

@WebServlet("/captcha")
public class CaptchaServlet extends HttpServlet {
    @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        StringBuilder code = new StringBuilder();
        BufferedImage img = CaptchaUtil.generate(code);
        
        // 存入 Session
        req.getSession().setAttribute("captcha_code", code.toString().toLowerCase());
        
        // 禁止快取
        resp.setHeader("Cache-Control", "no-store, no-cache");
        resp.setContentType("image/png");
        ImageIO.write(img, "PNG", resp.getOutputStream());
    }
}

二、中文漢字驗證碼(高安全性)

使用 Unicode CJK 統一表意文字基本區(0x4E00-0x9FA5)隨機選取常用字,並加入旋轉變形。

public class ChineseCaptchaUtil {
    private static final Random RANDOM = new Random();
    private static final int WIDTH = 130, HEIGHT = 40;
    private static final int CODE_LENGTH = 4;
    // 3500 常用字子集(實際可從文件加載)
    private static final String COMMON_CHARS = "的一是不了人我在有他這為之大來以個中們上..."; // 省略,需補全
    
    public static BufferedImage generate(StringBuilder codeHolder) {
        BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = image.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        
        // 背景漸變
        GradientPaint gp = new GradientPaint(0, 0, Color.WHITE, 0, HEIGHT, new Color(230, 240, 250));
        g.setPaint(gp);
        g.fillRect(0, 0, WIDTH, HEIGHT);
        
        // 邊框
        g.setColor(Color.LIGHT_GRAY);
        g.drawRect(0, 0, WIDTH-1, HEIGHT-1);
        
        // 干擾曲線
        g.setColor(new Color(180, 180, 200));
        for (int i = 0; i < 5; i++) {
            drawRandomCurve(g);
        }
        
        // 繪製漢字
        g.setFont(new Font("微軟雅黑", Font.BOLD, 30));
        StringBuilder code = new StringBuilder();
        for (int i = 0; i < CODE_LENGTH; i++) {
            char ch = COMMON_CHARS.charAt(RANDOM.nextInt(COMMON_CHARS.length()));
            code.append(ch);
            
            // 隨機旋轉 ±25 度
            double angle = (RANDOM.nextDouble() - 0.5) * Math.PI / 3.6; // ±25°
            int x = 20 + i * 28;
            int y = 30;
            
            AffineTransform original = g.getTransform();
            g.rotate(angle, x, y);
            g.setColor(randomDarkColor());
            g.drawString(String.valueOf(ch), x, y);
            g.setTransform(original); // 恢復變換
        }
        
        g.dispose();
        codeHolder.setLength(0);
        codeHolder.append(code);
        return image;
    }
    
    private static void drawRandomCurve(Graphics2D g) {
        int x1 = RANDOM.nextInt(WIDTH);
        int y1 = RANDOM.nextInt(HEIGHT);
        int x2 = RANDOM.nextInt(WIDTH);
        int y2 = RANDOM.nextInt(HEIGHT);
        int ctrlX = RANDOM.nextInt(WIDTH);
        int ctrlY = RANDOM.nextInt(HEIGHT);
        QuadCurve2D curve = new QuadCurve2D.Float(x1, y1, ctrlX, ctrlY, x2, y2);
        g.draw(curve);
    }
    
    private static Color randomDarkColor() {
        return new Color(RANDOM.nextInt(100), RANDOM.nextInt(100), RANDOM.nextInt(100));
    }
}

三、前端整合與校驗

<!-- HTML -->
<img id="captchaImg" src="/captcha" onclick="this.src='/captcha?t='+Date.now()" style="cursor:pointer">
<input type="text" id="captchaInput" placeholder="輸入驗證碼">

<script>
$('#captchaInput').on('blur', function() {
    $.post('/verify-captcha', {code: this.value}, function(res) {
        if (!res.valid) alert('驗證碼錯誤');
    });
});
</script>

後端校驗

@PostMapping("/verify-captcha")
@ResponseBody
public Map verify(@RequestParam String code, HttpSession session) {
    String real = (String) session.getAttribute("captcha_code");
    boolean valid = real != null && real.equalsIgnoreCase(code.trim());
    if (valid) session.removeAttribute("captcha_code"); // 一次性使用
    return Map.of("valid", valid);
}

安全加固建議

  1. 字符集去混淆:移除 0/O、1/I/l、8/B 等易混淆字符。
  2. 一次性失效:校驗成功後立即從 Session 移除。
  3. 限制請求頻率:同一 IP 每分鐘最多請求 N 次驗證碼圖片。
  4. HTTPS 傳輸:防止驗證碼圖片被中間人攔截分析。
  5. 無障礙備選:提供音頻驗證碼按鈕(TTS 朗讀)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved