Java實現滑塊拼圖驗證碼

本文實例為大傢分享瞭Java實現滑塊拼圖驗證碼的具體代碼,供大傢參考,具體內容如下

1、後端隨機生成摳圖和帶有摳圖陰影的背景圖片,後臺保存隨機摳圖位置坐標
2、前端實現滑動交互,將摳圖拼在摳圖陰影之上,獲取到用戶滑動距離值,比如以下示例

3、前端將用戶滑動距離值傳入後端,後端校驗誤差是否在容許范圍內。

這裡單純校驗用戶滑動距離是最基本的校驗,出於更高的安全考慮,可能還會考慮用戶滑動的整個軌跡,用戶在當前頁面的訪問行為等。這些可以很復雜,甚至借助到用戶行為數據分析模型,最終的目標都是增加非法的模擬和繞過的難度。這些有機會可以再歸納總結常用到的方法,本文重點集中在如何基於Java來一步步實現滑動驗證碼的生成。

可以看到,滑動圖形驗證碼,重要有兩個圖片組成,摳塊和帶有摳塊陰影的原圖,這裡面有兩個重要特性保證被暴力破解的難度:摳塊的形狀隨機和摳塊所在原圖的位置隨機。這樣就可以在有限的圖集中制造出隨機的、無規律可尋的摳圖和原圖的配對。

用代碼如何從一張大圖中摳出一個有特定隨機形狀的小圖呢?

第一步,先確定一個摳出圖的輪廓,方便後續真正開始執行圖片處理操作

圖片是有像素組成,每個像素點對應一種顏色,顏色可以用RGB形式表示,外加一個透明度,把一張圖理解成一個平面圖形,左上角為原點,向右x軸,向下y軸,一個坐標值對應該位置像素點的顏色,這樣就可以把一張圖轉換成一個二維數組。基於這個考慮,輪廓也用二維數組來表示,輪廓內元素值為1,輪廓外元素值對應0。

這時候就要想這個輪廓形狀怎麼生成瞭。有坐標系、有矩形、有圓形,沒錯,用到數學的圖形函數。典型用到一個圓的函數方程和矩形的邊線的函數,類似:

(x-a)²+(y-b)²=r²中,有三個參數a、b、r,即圓心坐標為(a,b),半徑r。這些將摳圖放在上文描述的坐標系上很容易就圖算出來具體的值。

示例代碼如下:

static int targetWidth = 55;//小圖長
static int targetHeight = 45;//小圖寬
static int circleR = 8;//半徑
static int r1 = 4;//距離點
 
    /**
     * @Createdate: 2019年1月24日上午10:52:42
     * @Title: getBlockData
     * @Description: 生成小圖輪廓
     * @author zhoujin
     * @return int[][]
     * @throws
     */
    private static int[][] getBlockData() {
        int[][] data = new int[targetWidth][targetHeight];
        double x2 = targetWidth -circleR; //47
 
        //隨機生成圓的位置
        double h1 = circleR + Math.random() * (targetWidth-3*circleR-r1);
        double po = Math.pow(circleR,2); //64
 
        double xbegin = targetWidth - circleR - r1;
        double ybegin = targetHeight- circleR - r1;
 
        //圓的標準方程 (x-a)²+(y-b)²=r²,標識圓心(a,b),半徑為r的圓
        //計算需要的小圖輪廓,用二維數組來表示,二維數組有兩張值,0和1,其中0表示沒有顏色,1有顏色
        for (int i = 0; i < targetWidth; i++) {
            for (int j = 0; j < targetHeight; j++) {
                double d2 = Math.pow(j - 2,2) + Math.pow(i - h1,2);
                double d3 = Math.pow(i - x2,2) + Math.pow(j - h1,2);
                if ((j <= ybegin && d2 < po)||(i >= xbegin && d3 > po)) {
                        data[i][j] = 0;
                }  else {
                        data[i][j] = 1;
                }
            }
        }
        return data;
    }

第二步,有這個輪廓後就可以依據這個二維數組的值來判定摳圖並在原圖上摳圖位置處加陰影。

操作如下:

/**
     *
     * @Createdate: 2019年1月24日上午10:51:30
     * @Title: cutByTemplate
     * @Description: 有這個輪廓後就可以依據這個二維數組的值來判定摳圖並在原圖上摳圖位置處加陰影,
     * @author zhoujin
     * @param oriImage  原圖
     * @param targetImage  摳圖拼圖
     * @param templateImage 顏色
     * @param x
     * @param y void
     * @throws
     */
    private static void cutByTemplate(BufferedImage oriImage, BufferedImage targetImage, int[][] templateImage, int x, int y){
        int[][] martrix = new int[3][3];
        int[] values = new int[9];
        //創建shape區域
        for (int i = 0; i < targetWidth; i++) {
            for (int j = 0; j < targetHeight; j++) {
                int rgb = templateImage[i][j];
                // 原圖中對應位置變色處理
                int rgb_ori = oriImage.getRGB(x + i, y + j);
 
                if (rgb == 1) {
                    targetImage.setRGB(i, j, rgb_ori);
 
                    //摳圖區域高斯模糊
                    readPixel(oriImage, x + i, y + j, values);
                    fillMatrix(martrix, values);
                    oriImage.setRGB(x + i, y + j, avgMatrix(martrix));
                }else{
                    //這裡把背景設為透明
                    targetImage.setRGB(i, j, rgb_ori & 0x00ffffff);
                }
            }
        }
    }
 
 
   private static void readPixel(BufferedImage img, int x, int y, int[] pixels) {
        int xStart = x - 1;
        int yStart = y - 1;
        int current = 0;
        for (int i = xStart; i < 3 + xStart; i++)
            for (int j = yStart; j < 3 + yStart; j++) {
                int tx = i;
                if (tx < 0) {
                    tx = -tx;
 
                } else if (tx >= img.getWidth()) {
                    tx = x;
                }
                int ty = j;
                if (ty < 0) {
                    ty = -ty;
                } else if (ty >= img.getHeight()) {
                    ty = y;
                }
                pixels[current++] = img.getRGB(tx, ty);
 
            }
    }
 
    private static void fillMatrix(int[][] matrix, int[] values) {
        int filled = 0;
        for (int i = 0; i < matrix.length; i++) {
            int[] x = matrix[i];
            for (int j = 0; j < x.length; j++) {
                x[j] = values[filled++];
            }
        }
    }
 
    private static int avgMatrix(int[][] matrix) {
        int r = 0;
        int g = 0;
        int b = 0;
        for (int i = 0; i < matrix.length; i++) {
            int[] x = matrix[i];
            for (int j = 0; j < x.length; j++) {
                if (j == 1) {
                    continue;
                }
                Color c = new Color(x[j]);
                r += c.getRed();
                g += c.getGreen();
                b += c.getBlue();
            }
        }
        return new Color(r / 8, g / 8, b / 8).getRGB();
    }

經過前面兩步後,就得到瞭摳圖和帶高斯模糊摳圖陰影的原圖。返回生成的摳圖和帶陰影的大圖base64碼及摳圖坐標。

/**
     * @Description: 讀取本地圖片,生成拼圖驗證碼
     * @author zhoujin
     * @return Map<String,Object>  返回生成的摳圖和帶摳圖陰影的大圖 base64碼及摳圖坐標
     */
    public static Map<String,Object> createImage(File file, Map<String,Object> resultMap){
        try {
            BufferedImage oriImage = ImageIO.read(file);
            Random random = new Random();
            //X軸距離右端targetWidth  Y軸距離底部targetHeight以上
            int widthRandom = random.nextInt(oriImage.getWidth()-  2*targetWidth) + targetWidth;
            int heightRandom = random.nextInt(oriImage.getHeight()- targetHeight);
            logger.info("原圖大小{} x {},隨機生成的坐標 X,Y 為({},{})",oriImage.getWidth(),oriImage.getHeight(),widthRandom,heightRandom);
 
            BufferedImage targetImage= new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_4BYTE_ABGR);
            cutByTemplate(oriImage,targetImage,getBlockData(),widthRandom,heightRandom);
 
            resultMap.put("bigImage", getImageBASE64(oriImage));//大圖
            resultMap.put("smallImage", getImageBASE64(targetImage));//小圖
            resultMap.put("xWidth",widthRandom);
            resultMap.put("yHeight",heightRandom);
        } catch (Exception e) {
            logger.info("創建圖形驗證碼異常",e);
        } finally{
            return resultMap;
        }
    }
 
 
    /**
     * @Description: 讀取網絡圖片,生成拼圖驗證碼
     * @author zhoujin
     * @return Map<String,Object>  返回生成的摳圖和帶摳圖陰影的大圖 base64碼及摳圖坐標
     */
    public static Map<String,Object> createImage(String imgUrl, Map<String,Object> resultMap){
        try {
            //通過URL 讀取圖片
            URL url = new URL(imgUrl);
            BufferedImage bufferedImage = ImageIO.read(url.openStream());
            Random rand = new Random();
            int widthRandom = rand.nextInt(bufferedImage.getWidth()-  targetWidth - 100 + 1 ) + 100;
            int heightRandom = rand.nextInt(bufferedImage.getHeight()- targetHeight + 1 );
            logger.info("原圖大小{} x {},隨機生成的坐標 X,Y 為({},{})",bufferedImage.getWidth(),bufferedImage.getHeight(),widthRandom,heightRandom);
 
            BufferedImage target= new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_4BYTE_ABGR);
            cutByTemplate(bufferedImage,target,getBlockData(),widthRandom,heightRandom);
            resultMap.put("bigImage", getImageBASE64(bufferedImage));//大圖
            resultMap.put("smallImage", getImageBASE64(target));//小圖
            resultMap.put("xWidth",widthRandom);
            resultMap.put("yHeight",heightRandom);
        } catch (Exception e) {
            logger.info("創建圖形驗證碼異常",e);
        } finally{
            return resultMap;
        }
    }
 
 
    /**
     * @Title: getImageBASE64
     * @Description: 圖片轉BASE64
     * @author zhoujin
     * @param image
     * @return
     * @throws IOException String
     */
    public static String getImageBASE64(BufferedImage image) throws IOException {
        byte[] imagedata = null;
        ByteArrayOutputStream bao=new ByteArrayOutputStream();
        ImageIO.write(image,"png",bao);
        imagedata=bao.toByteArray();
        BASE64Encoder encoder = new BASE64Encoder();
        String BASE64IMAGE=encoder.encodeBuffer(imagedata).trim();
        BASE64IMAGE = BASE64IMAGE.replaceAll("\r|\n", "");  //刪除 \r\n
        return BASE64IMAGE;
    }

控制層代碼實現及校驗驗證碼:

/**
     * @param @return 參數說明
     * @return BaseRestResult 返回類型
     * @Description: 生成滑塊拼圖驗證碼
     */
    @RequestMapping(value = "/getImageVerifyCode.do", method = RequestMethod.GET, produces = {"application/json;charset=UTF-8"})
    public BaseRestResult getImageVerifyCode() {
        Map<String, Object> resultMap = new HashMap<>();
        //讀取本地路徑下的圖片,隨機選一條
        File file = new File(this.getClass().getResource("/image").getPath());
        File[] files = file.listFiles();
        int n = new Random().nextInt(files.length);
        File imageUrl = files[n];
        ImageUtil.createImage(imageUrl, resultMap);
 
        //讀取網絡圖片
        //ImageUtil.createImage("/7986d66f29bfeb6015aaaec33d33fcd1d875ca16316f-2bMHNG_fw658",resultMap);
        session.setAttribute("xWidth", resultMap.get("xWidth"));
        resultMap.remove("xWidth");
        resultMap.put("errcode", 0);
        resultMap.put("errmsg", "success");
        return new BaseRestResult(resultMap);
    }
 
 
    /**
     * 校驗滑塊拼圖驗證碼
     *
     * @param moveLength 移動距離
     * @return BaseRestResult 返回類型
     * @Description: 生成圖形驗證碼
     */
    @RequestMapping(value = "/verifyImageCode.do", method = RequestMethod.GET, produces = {"application/json;charset=UTF-8"})
    public BaseRestResult verifyImageCode(@RequestParam(value = "moveLength") String moveLength) {
        Double dMoveLength = Double.valueOf(moveLength);
        Map<String, Object> resultMap = new HashMap<>();
        try {
            Integer xWidth = (Integer) session.getAttribute("xWidth");
            if (xWidth == null) {
                resultMap.put("errcode", 1);
                resultMap.put("errmsg", "驗證過期,請重試");
                return new BaseRestResult(resultMap);
            }
            if (Math.abs(xWidth - dMoveLength) > 10) {
                resultMap.put("errcode", 1);
                resultMap.put("errmsg", "驗證不通過");
            } else {
                resultMap.put("errcode", 0);
                resultMap.put("errmsg", "驗證通過");
            }
        } catch (Exception e) {
            throw new EsServiceException(e.getMessage());
        } finally {
            session.removeAttribute("xWidth");
        }
        return new BaseRestResult(resultMap);
}

前端顯示圖片代碼:

<img src="data:image/png;base64,+返回的base64圖片碼" alt="摳圖">
<img src="data:image/png;base64,+返回的base64圖片碼" alt="帶摳圖陰影的原圖">

至此後臺java實現滑塊驗證碼關鍵代碼完成!

以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。

推薦閱讀: