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

Java 實現仿 12306 風格圖片驗證碼(點選圖片驗證)

     編輯:Java 相關知識

設計思路

參考 12306 驗證碼交互模式:服務端從素材庫隨機抽取 8 張小圖拼合成大圖,並隨機指定一類目標圖片(如「小狗」、「啤酒」)作為驗證目標。前端展示大圖,用戶點擊所有屬於目標類別的小圖,提交坐標後端驗證。

核心流程

  1. 素材庫組織:二級目錄分類存放圖片,目錄名即類別標籤。
  2. 隨機抽取 8 張不重複圖片:遞歸去重保證 8 張圖片來自不同類別或同類別不同圖片。
  3. 確定驗證目標類別:從 8 張圖片的類別中隨機選一個作為 Key,記錄該類別在 8 張圖中的位置(1-8)。
  4. 圖片拼合:BufferedImage.setRGB() 將 8 張 200x200 小圖拼成 800x400 大圖(2 行 4 列)。
  5. 前端交互:用戶點擊大圖,JS 計算相對坐標,轉換為小圖索引(1-8),標記選中狀態。
  6. 後端驗證:根據提交坐標反解小圖索引,與 Session 存儲的 Key 位置列表比對,全對通過。
  7. 刷新機制:AJAX 無法直接處理二進制流,採用 XMLHttpRequest + responseType=blob + createObjectURL 實現無刷新更新圖片。

關鍵代碼片段

1. 抽取 8 張圖片並去重

public static List<Object> getEightImages() {
    List<String> paths = new ArrayList<>();
    File[] finalImages = new File[8];
    String[] tips = new String[8];
    for (int i = 0; i < 8; i++) {
        int dirIndex = getRandom(secondaryDirNumbers);
        File secondaryDir = getFiles()[dirIndex];
        tips[i] = secondaryDir.getName(); // 類別標籤
        File[] images = secondaryDir.listFiles();
        File image = images[getRandom(images.length)];
        image = dropSameImage(image, paths, tips, i); // 遞歸去重
        paths.add(image.getPath());
        finalImages[i] = image;
    }
    List<Object> obj = new ArrayList<>();
    obj.add(finalImages);
    obj.add(tips);
    return obj;
}

2. 確定目標類別及位置

public static List<Object> getLocation(String[] tips) {
    List<Object> loc = new ArrayList<>();
    String tip = getTip(tips); // 隨機選一個類別作為 Key
    loc.add(tip);
    for (int i = 0; i < tips.length; i++) {
        if (tip.equals(tips[i])) loc.add(i + 1); // 記錄位置(1-based)
    }
    return loc;
}

3. 圖片拼合輸出

public static void mergeImage(File[] finalImages, HttpServletResponse response) throws IOException {
    BufferedImage mergeImage = new BufferedImage(800, 400, BufferedImage.TYPE_INT_BGR);
    for (int i = 0; i < 8; i++) {
        BufferedImage bi = ImageIO.read(finalImages[i]);
        int[] rgb = bi.getRGB(0, 0, bi.getWidth(), bi.getHeight(), null, 0, bi.getWidth());
        if (i < 4) mergeImage.setRGB(i * 200, 0, bi.getWidth(), bi.getHeight(), rgb, 0, bi.getWidth());
        else mergeImage.setRGB((i - 4) * 200, 200, bi.getWidth(), bi.getHeight(), rgb, 0, bi.getWidth());
    }
    ImageIO.write(mergeImage, "jpg", response.getOutputStream());
}

4. Controller 層處理

@GetMapping("/identify")
public void identify(HttpSession session, HttpServletResponse response) throws IOException {
    response.setContentType("image/jpeg");
    response.setHeader("Pragma", "No-cache");
    response.setHeader("Cache-Control", "no-cache");
    response.setDateHeader("Expires", 0);

    List<Object> obj = ImageSelectedHelper.getEightImages();
    File[] images = (File[]) obj.get(0);
    String[] tips = (String[]) obj.get(1);
    List<Object> loc = ImageSelectedHelper.getLocation(tips);

    session.setAttribute("tip", loc.get(0)); // 目標類別
    loc.remove(0);
    session.setAttribute("locations", loc); // 目標位置列表

    ImageMerge.mergeImage(images, response);
}

5. 前端點擊處理

function clickImg(e) {
    var baseDiv = document.getElementById("base");
    var rect = baseDiv.getBoundingClientRect();
    var clickX = e.clientX - rect.left;
    var clickY = e.clientY - rect.top;
    // 根據 clickX, clickY 計算小圖索引 (1-8),創建標記 div+img 顯示選中
    // 將選中索引存入隱藏域或 JS 數組,提交時發送
}

6. 後端坐標驗證

public List<Integer> isPass(String result) {
    String[] xyLocations = result.split(",");
    List<Integer> userSel = new ArrayList<>();
    for (String xy : xyLocations) {
        int x = Integer.parseInt(xy.split("\\|\\|")[0]);
        int y = Integer.parseInt(xy.split("\\|\\|")[1]);
        // 根據 800x400 大圖中 200x200 小圖區域判斷索引
        if (x > -75 && x <= 0) { if (y > -75 && y <= 0) userSel.add(8); else if (y >= -150 && y <= -75) userSel.add(4); }
        else if (x > -150 && x <= -75) { if (y > -75 && y <= 0) userSel.add(7); else if (y >= -150 && y <= -75) userSel.add(3); }
        else if (x > -225 && x <= -150) { if (y > -75 && y <= 0) userSel.add(6); else if (y >= -150 && y <= -75) userSel.add(2); }
        else if (x >= -300 && x <= -225) { if (y > -75 && y <= 0) userSel.add(5); else if (y >= -150 && y <= -75) userSel.add(1); }
        else return null;
    }
    return userSel;
}

7. Blob 刷新圖片

function refresh() {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', '/identify', true);
    xhr.responseType = 'blob';
    xhr.onload = function() {
        if (this.status === 200) {
            var blob = this.response;
            bigPicture.onload = () => URL.revokeObjectURL(bigPicture.src);
            bigPicture.src = URL.createObjectURL(blob);
        }
    };
    xhr.send();
}

優化建議

  • 素材庫需足夠豐富(建議 50+ 類別,每類 20+ 圖片)防止機器學習破解。
  • 引入圖片幹擾(噪點、模糊、旋轉)提高攻擊成本。
  • 前端坐標計算使用 getBoundingClientRect() 替代手動累加 offset,兼容性更好。
  • 後端驗證邏輯建議封裝為通用工具類,支持可配置網格大小。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved